Compare commits
74 commits
df/accessi
...
master
Author | SHA1 | Date | |
---|---|---|---|
6c225f26ee | |||
27ca1c9f9e | |||
d3540a57d3 | |||
44d813c902 | |||
b01a79b9fa | |||
571894a6a4 | |||
bb20d8d222 | |||
aca436a4e6 | |||
bfb3b55fda | |||
ffff60e036 | |||
848f2d79ae | |||
bbf18323ea | |||
4adc38883c | |||
19b67010cc | |||
4b67ef3b9d | |||
d9deb85b45 | |||
ab5649a531 | |||
9daa276c2e | |||
f921721f32 | |||
f2d27568a1 | |||
9194c58444 | |||
8a847c9a3f | |||
0991cfdcdf | |||
b4d021b22a | |||
1a90ac8620 | |||
186ba0261a | |||
2d38e11cc7 | |||
9904d770af | |||
ac1f03bec0 | |||
6dd8d36f57 | |||
ef6e306cb3 | |||
2e332e20ea | |||
4b37f740b3 | |||
43a41fdcac | |||
84d7289507 | |||
6becc36f49 | |||
e08d3bbb48 | |||
b8a829e0a1 | |||
aa27b1a257 | |||
83f31c5da4 | |||
7b844b6153 | |||
7b91f1f2a7 | |||
df28a035dd | |||
ef7be7d6cd | |||
d626607517 | |||
42ed8c29f4 | |||
da6a82df93 | |||
da8647fc29 | |||
e0f35f5b53 | |||
e99434ed3d | |||
2a0d16cc2f | |||
63fcb71706 | |||
ad4e8ef09f | |||
818889f55a | |||
c139163a91 | |||
7631a68d9a | |||
91701b25f5 | |||
0074b5cc4a | |||
c8c021b527 | |||
418766dca5 | |||
04d855724b | |||
55d3287259 | |||
3911b5fe19 | |||
b775ed86cd | |||
62166c3ddb | |||
155b7d5c78 | |||
27b382ffb9 | |||
178e6a92fa | |||
eee76391e2 | |||
b3d2da594d | |||
2f7634e20e | |||
971f8f8564 | |||
313bb31537 | |||
6aff13706a |
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use flake
|
4
.gitignore
vendored
|
@ -1,6 +1,10 @@
|
|||
/.direnv
|
||||
/result
|
||||
/resources
|
||||
/public
|
||||
/node_modules
|
||||
|
||||
/.netlify
|
||||
|
||||
*.lock
|
||||
!flake.lock
|
||||
|
|
20
config.yaml
|
@ -4,16 +4,22 @@ title: lyte.dev
|
|||
pygmentsCodeFences: true
|
||||
pygmentsCodeFencesGuessSyntax: true
|
||||
pygmentsUseClasses: true
|
||||
enableGitInfo: true
|
||||
markup:
|
||||
goldmark:
|
||||
renderer:
|
||||
unsafe: true
|
||||
|
||||
tableOfContents:
|
||||
endLevel: 3
|
||||
ordered: false
|
||||
startLevel: 1
|
||||
|
||||
# permalinks:
|
||||
# post: /blog/:title
|
||||
|
||||
params:
|
||||
Description: "Hi! I'm Daniel. I live in Kansas City where I help run a small Christian church, raise two kids with my awesome wife, and write software for Divvy."
|
||||
Description: "Hi! I'm Daniel. I live in Kansas City where I help run a small Christian church, raise three boys with my awesome wife, and write software."
|
||||
|
||||
outputs:
|
||||
home: [html]
|
||||
|
@ -23,13 +29,17 @@ menu:
|
|||
main:
|
||||
- identifier: about
|
||||
name: about
|
||||
url: /about
|
||||
url: /about/
|
||||
weight: 10
|
||||
- identifier: blog
|
||||
name: blog
|
||||
url: /blog
|
||||
url: /blog/
|
||||
weight: 20
|
||||
# - identifier: tips
|
||||
# name: tips
|
||||
# url: /tips/
|
||||
# weight: 30
|
||||
- identifier: contact
|
||||
name: contact
|
||||
url: /contact
|
||||
weight: 30
|
||||
url: /contact/
|
||||
weight: 40
|
||||
|
|
|
@ -1,23 +1,28 @@
|
|||
## Hi! I'm Daniel.
|
||||
|
||||
<img alt="photo of daniel" class="rounded" src="/img/avatar2-512.webp" height=256 width=256 />
|
||||
<img height=256 width=256 alt="photo of daniel" class="rounded"
|
||||
src="/img/avatar3-square-512.webp"
|
||||
srcset="/img/avatar3-square-256.webp, /img/avatar3-square-512.webp 2x" />
|
||||
|
||||
<!-- updates here must be mirrored in site config for meta description-->
|
||||
|
||||
I live in Kansas City where I help run <a target="_blank"
|
||||
href="https://kcrising.church">a small Christian church</a>, raise two kids
|
||||
href="https://kcrising.church">a small Christian church</a>, raise three boys
|
||||
with my <a target="_blank"
|
||||
href="https://www.instagram.com/valerielauren93">awesome wife</a>, and write
|
||||
software for <a target="_blank" href="https://getdivvy.com">Divvy</a>.
|
||||
software for <a target="_blank" href="https://getdivvy.com">Divvy (acquired by
|
||||
Bill.com)</a>.
|
||||
|
||||
I run a ton of self-hosted software here at home on some machines that sit on
|
||||
[a server rack in my basement][rack] and am in the process of setting up a tiny
|
||||
Kubernetes cluster. I love building [keyboards][kb], too. [I also heavily
|
||||
customize my workflow][wf] and you can sift through my [dotfiles][df].
|
||||
I run a lot of self-hosted software here at home on some machines that sit on
|
||||
[an unnecessarily large server rack in my basement][rack]. I love building
|
||||
[keyboards][kb], too. [I heavily customize my workflow][wf] and you can
|
||||
see how I set everything up with [Nix][nix] if you like (or even my old
|
||||
[dotfiles][df]).
|
||||
|
||||
Occasionally, I post technical articles here.
|
||||
Occasionally, I post articles here.
|
||||
|
||||
[rack]: //files.lyte.dev/unix/server-rack.jpg
|
||||
[rack]: //files.lyte.dev/images/server-rack-angle-2023-07.jpg
|
||||
[kb]: //files.lyte.dev/keyboards
|
||||
[wf]: //files.lyte.dev/unix/desktop-screenshot.png
|
||||
[wf]: //files.lyte.dev/images/desktop-screenshot-busy-2023-07.png
|
||||
[nix]: //git.lyte.dev/lytedev/nix
|
||||
[df]: //git.lyte.dev/lytedev/dotfiles
|
||||
|
|
|
@ -6,6 +6,7 @@ imageOverlayOpacity: 0.6
|
|||
heroBackgroundColor: "#0af"
|
||||
title: About
|
||||
description: "A little about the man behind this website."
|
||||
toc: false
|
||||
---
|
||||
|
||||
My name is Daniel Flanagan. I was born in Virginia (but my family didn't stay
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
title: Blog
|
||||
---
|
||||
|
||||
## Latest Posts ([RSS](/blog/index.xml))
|
||||
## Latest Blog Posts ([RSS](/blog/index.xml))
|
||||
|
|
9
content/blog/contributor-license-agreements.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: "Contributor License Agreements"
|
||||
date: "2024-06-12"
|
||||
draft: true
|
||||
---
|
||||
|
||||
# Other interesting points on CLAs:
|
||||
|
||||
- Some people cannot contribute when CLAs are in the way because they cannot give out "perpetual" or "unrestricted" licenses to their work due to agreements with their own employer or place of education: https://utcc.utoronto.ca/~cks/space/blog/tech/CLAsImpedeContributions
|
|
@ -35,7 +35,7 @@ can either use [`Ecto.Changeset.change/2`][Ecto.Changeset.change/2] to handle
|
|||
the results of an admin user submitting those forms or implement some sort of
|
||||
protocol that lets you specify an admin-specific changeset.
|
||||
|
||||
## Getting That Sweet, Sweet Metadata
|
||||
# Getting That Sweet, Sweet Metadata
|
||||
|
||||
My first issue was figuring out how to get the metadata that I knew Ecto already
|
||||
had about my schemas. Y'know, which fields are which types, so that I could use
|
||||
|
@ -301,7 +301,7 @@ single schema you have, too! Ahh, simplicity.
|
|||
|
||||
Here's all the code jumbled together (and perhaps slightly different):
|
||||
|
||||
## All Together Now!
|
||||
# All Together Now!
|
||||
|
||||
```elixir
|
||||
# router.ex
|
||||
|
|
|
@ -22,7 +22,7 @@ Check it out in context on
|
|||
<a href="https://ellie-app.com/53ypTnnFykXa1" target="_blank">Ellie</a>,
|
||||
the Elm playground!
|
||||
|
||||
## How Did I Get Here?
|
||||
# How Did I Get Here?
|
||||
|
||||
To preface this, I'm going to be writing a *lot* of forms with a *lot* of
|
||||
fields, all of which will pretty much work exactly the same, so creating a layer
|
||||
|
@ -42,7 +42,7 @@ of copying code from it, I'm just going to roll my own, which is generally
|
|||
a really good way to learn something anyways. So, while frustrated, I was also
|
||||
eager to tackle the problem.
|
||||
|
||||
## Code Already!
|
||||
# Code Already!
|
||||
|
||||
Since the Elm compiler is so awesome, I've taken to what I'm calling
|
||||
"compiler-driven development", which is where I write out what I expect to work
|
||||
|
@ -248,7 +248,7 @@ do (computers, amirite?) before I made it to this point.
|
|||
Now, I'm confident Elm is a tool that can do everything I need it to do. I look
|
||||
forward to using it more in the future!
|
||||
|
||||
## Full Source
|
||||
# Full Source
|
||||
|
||||
Check it out in context on
|
||||
<a href="https://ellie-app.com/53ypTnnFykXa1" target="_blank">Ellie</a>,
|
||||
|
|
63
content/blog/go-mod-proxy.md
Normal file
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: "Fetching Go Modules via `goproxy` Inside VPN"
|
||||
date: "2024-02-29"
|
||||
toc: false
|
||||
---
|
||||
|
||||
I think I finally setup the holy grail of universally being able to
|
||||
fetch-by-proxy go modules through a firewall using
|
||||
https://github.com/goproxy/goproxy
|
||||
|
||||
<!--more-->
|
||||
|
||||
On your internal host (such as your work machine), run the following:
|
||||
|
||||
```shell_session
|
||||
GOPRIVATE=git.company.com GOMODCACHE=~/go goproxy server --address localhost:9981
|
||||
```
|
||||
|
||||
On your external host (such as a network isolated Linux VM):
|
||||
|
||||
```shell_session
|
||||
ssh -L 9981:localhost:9981 $INTERNALHOST &
|
||||
GOPROXY=http://localhost:9981,direct go mod tidy
|
||||
```
|
||||
|
||||
Of course, the tunneling is optional and you can use a non-`localhost`
|
||||
`--address` when running `goproxy server`, but then of course you are dealing
|
||||
with this proxy being open on the LAN, which may upset security in some cases.
|
||||
|
||||
And bam! Now you can fetch go modules as if you're on the VPN even if you're not
|
||||
on the VPN.
|
||||
|
||||
You can use something like `go env -w GOPROXY=http://localhost:9981,direct` to
|
||||
avoid prefixing all your `go` commands with the environment variable. Obviously,
|
||||
this can cause things to break weirdly if/when the `goproxy server` dies or the
|
||||
tunnel is disconnected. Tread lightly!
|
||||
|
||||
One last possible step is that when the proxy machine clones the repo it may try
|
||||
to do so over HTTPS when you almost certainly want it to use SSH. To avoid this,
|
||||
you can do something like this in `~/.gitconfig` or `~/.config/git/config` to
|
||||
force git to use SSH instead of HTTPS:
|
||||
|
||||
```ini
|
||||
[url "git@git.example.com:"]
|
||||
insteadOf = "https://git.example.com"
|
||||
```
|
||||
|
||||
My full invocation looks something like this:
|
||||
|
||||
```shell_session
|
||||
go install github.com/goproxy/goproxy/cmd/goproxy@latest
|
||||
# put this cute background job somewhere or `disown`
|
||||
GOPRIVATE=git.example.com GOMODCACHE=~/go goproxy server --address localhost:58320 &
|
||||
```
|
||||
|
||||
And then on the client:
|
||||
|
||||
```shell_session
|
||||
# put this cute background job somewhere or `disown`
|
||||
ssh -L 58320:localhost:58320 $PROXYHOST &
|
||||
go env -w GOPROXY=http://localhost:58320,direct
|
||||
go mod tidy
|
||||
```
|
|
@ -4,10 +4,14 @@ title: How to Setup A Free, Fast, and Simple Blog Like This
|
|||
draft: true
|
||||
---
|
||||
|
||||
<!-- better intro and summary? -->
|
||||
|
||||
**TL;DR**: Netlify is incredible for hosting static websites. The code for this
|
||||
entire site is available [here][repo]. I publish at-will using their CLI via
|
||||
`netlify deploy --prod`. It's simple and awesome.
|
||||
|
||||
<!--more-->
|
||||
|
||||
# Introduction
|
||||
|
||||
This website may not look like much, but that's *intentional*. It is simple
|
||||
|
|
21
content/blog/iex-dbg-pry.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: "iex and dbg/1 without pry prompts"
|
||||
date: "2023-06-22"
|
||||
toc: false
|
||||
---
|
||||
|
||||
I love `iex -S mix ...` but I usually don't like when `dbg` asks me to `pry`.
|
||||
Just show me my data! Well, today I learned about `iex --no-pry`:
|
||||
|
||||
<!--more-->
|
||||
|
||||
```console
|
||||
$ iex --help
|
||||
Usage: iex [options] [.exs file] [data]
|
||||
...
|
||||
--no-pry Doesn't start pry sessions when dbg/2 is called.
|
||||
```
|
||||
|
||||
Now I can `iex --no-pry -S mix ...` and just see output instead of dealing with
|
||||
the prompts for `pry`ing! Not sure if anybody else felt this pain, but there
|
||||
ya go.
|
382
content/blog/learn-flakes-the-fun-way.md
Normal file
|
@ -0,0 +1,382 @@
|
|||
---
|
||||
date: "2024-07-08T16:34:00-05:00"
|
||||
title: "Learn Flakes the Fun Way"
|
||||
draft: false
|
||||
toc: false
|
||||
---
|
||||
|
||||
This post is a Flake-based rewrite of [Learn Nix the Fun Way on fzakaria.com][0].
|
||||
I really enjoyed the content of the post and wanted to write it as a Nix
|
||||
user who is just using and prefers Flakes. It does add a few extra steps and
|
||||
complexity, but I think it's still valuable and perhaps reveals a bit more about
|
||||
Nix and why it's pretty fantastic. I also include a few neat things that are
|
||||
more Flake-specific at the end.
|
||||
|
||||
But yes, it's basically plagiarism.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## what-is-my-ip
|
||||
|
||||
Let's walk through a single example of a shell script one may write, `what-is-my-ip`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
curl -s http://httpbin.org/get | jq --raw-output .origin
|
||||
```
|
||||
|
||||
Sure, it's _sort of portable_, if you tell the person running it to have _curl_ and _jq_. What if you relied on a specific version of either, though?
|
||||
|
||||
Nix **guarantees** portability.
|
||||
|
||||
We might leverage _[Nixpkgs' trivial builders](https://ryantm.github.io/nixpkgs/builders/trivial-builders/)_ (specifically, `writeShellScriptBin`) in a basic Nix Flake to turn this into a Nix derivation (i.e. build recipe):
|
||||
|
||||
```nix
|
||||
# flake.nix
|
||||
{
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-24.05";
|
||||
outputs = {nixpkgs, ...}: let
|
||||
systems = ["aarch64-linux" "aarch64-darwin" "x86_64-darwin" "x86_64-linux"];
|
||||
pkgsFor = func: (nixpkgs.lib.genAttrs systems (system: (func (import nixpkgs {inherit system;}))));
|
||||
in {
|
||||
packages = pkgsFor (pkgs: {
|
||||
default = pkgs.callPackage ./what-is-my-ip.nix {};
|
||||
});
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
```nix
|
||||
# what-is-my-ip.nix
|
||||
{pkgs}:
|
||||
pkgs.writeShellScriptBin "what-is-my-ip" ''
|
||||
${pkgs.curl}/bin/curl -s http://httpbin.org/get | ${pkgs.jq}/bin/jq --raw-output .origin
|
||||
''
|
||||
```
|
||||
|
||||
> 😬 Avoid over-focusing on the fact I just introduced a new language and a good chunk of boilerplate. Just come along for the ride.
|
||||
|
||||
Here we are pinning our package to dependencies which come from NixOS/Nixpkgs release branch 24.05.
|
||||
|
||||
We can build our package and find out the Nix store path (which contains the
|
||||
hash) like so:
|
||||
|
||||
```console
|
||||
$ nix build --print-out-paths
|
||||
/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip
|
||||
```
|
||||
|
||||
And, of course, we can run our built result:
|
||||
|
||||
```console
|
||||
$ ./result/bin/what-is-my-ip
|
||||
24.5.113.148
|
||||
```
|
||||
|
||||
Or run it from the Flake directly:
|
||||
|
||||
```console
|
||||
$ nix run
|
||||
24.5.113.148
|
||||
```
|
||||
|
||||
### Graph Dependencies
|
||||
|
||||
Now that this is in our Nix store, we've naturally modeled our dependencies
|
||||
and can do _fun_ things like generate graph diagrams:
|
||||
|
||||
```console
|
||||
$ nix-store --query --graph $(readlink result) | nix shell nixpkgs#graphviz -c dot -Tpng -o what-is-my-ip-deps.png
|
||||
```
|
||||
|
||||
[![Image of what-is-my-ip dependencies as a graph](/img/what-is-my-ip-deps.png)](/img/what-is-my-ip-deps.png)
|
||||
|
||||
### Developer Environments
|
||||
|
||||
Let's add a _developer environment_ which contains our new tool.
|
||||
This is a great way to create developer environments with reproducible tools.
|
||||
Generally, though, `devShells` are for working _on_ the product of the Flake and
|
||||
do not _usually_ include the product itself.
|
||||
|
||||
```diff
|
||||
diff --git a/flake.nix b/flake.nix
|
||||
index 2a99357..ab32421 100644
|
||||
--- a/flake.nix
|
||||
+++ b/flake.nix
|
||||
@@ -1,11 +1,24 @@
|
||||
{
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-24.05";
|
||||
- outputs = {nixpkgs, ...}: let
|
||||
+ outputs = {
|
||||
+ self, # we will need to reference our own outputs to pull in the package we've declared
|
||||
+ nixpkgs,
|
||||
+ ...
|
||||
+ }: let
|
||||
systems = ["aarch64-linux" "aarch64-darwin" "x86_64-darwin" "x86_64-linux"];
|
||||
pkgsFor = func: (nixpkgs.lib.genAttrs systems (system: (func (import nixpkgs {inherit system;}))));
|
||||
in {
|
||||
packages = pkgsFor (pkgs: {
|
||||
default = pkgs.callPackage ./what-is-my-ip.nix {};
|
||||
});
|
||||
+
|
||||
+ devShells = pkgsFor (pkgs: {
|
||||
+ default = pkgs.mkShell {
|
||||
+ packages = [self.outputs.packages.${pkgs.system}.default];
|
||||
+ shellHook = ''
|
||||
+ echo "Hello, Nix!"
|
||||
+ '';
|
||||
+ };
|
||||
+ });
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ nix develop -c $SHELL
|
||||
Hello, Nix!
|
||||
|
||||
$ which what-is-my-ip
|
||||
/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip/bin/what-is-my-ip
|
||||
```
|
||||
|
||||
🕵️ Notice that the hash `lr6wlz2652r35rwzc79samg77l6iqmii` is _exactly_ the same which we built earlier.
|
||||
|
||||
### Deployment
|
||||
|
||||
We can now do binary or source deployments 🚀🛠️📦 since we know the full dependency closure of our tool. We simply copy the necessary `/nix/store` paths to another machine with Nix installed.
|
||||
|
||||
```console
|
||||
$ nix copy --to ssh://beefcake $(nix build --print-out-paths)
|
||||
$ ssh beefcake /nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip/bin/what-is-my-ip
|
||||
98.147.178.19
|
||||
```
|
||||
|
||||
#### With Containers
|
||||
|
||||
Maybe though you are stuck with Kubernetes or Docker. Let's use Nix to create an OCI-compatible image with our tool:
|
||||
|
||||
```diff
|
||||
diff --git a/flake.nix b/flake.nix
|
||||
index 99d6d52..81e98c9 100644
|
||||
--- a/flake.nix
|
||||
+++ b/flake.nix
|
||||
@@ -10,6 +10,12 @@
|
||||
in {
|
||||
packages = pkgsFor (pkgs: {
|
||||
default = pkgs.callPackage ./what-is-my-ip.nix {};
|
||||
+ container = pkgs.dockerTools.buildImage {
|
||||
+ name = "what-is-my-ip-container";
|
||||
+ config = {
|
||||
+ Cmd = ["${self.outputs.packages.${pkgs.system}.default}/bin/what-is-my-ip"];
|
||||
+ };
|
||||
+ };
|
||||
});
|
||||
|
||||
devShells = pkgsFor (pkgs: {
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker load < $(nix build .#container --print-out-paths)
|
||||
Loaded image: what-is-my-ip-docker:c9g6x30invdq1bjfah3w1aw5w52vkdfn
|
||||
$ docker run -it what-is-my-ip-container:c9g6x30invdq1bjfah3w1aw5w52vkdfn
|
||||
24.5.113.148
|
||||
```
|
||||
|
||||
Cool! Nix + Docker integration perfectly. The image produced has only the files exactly necessary to run the tool provided, effectively **distroless**. You may also note that if you are following along, your image digest is exactly the same. **Reproducibility!**
|
||||
|
||||
#### In Virtual Machines
|
||||
|
||||
Finally, let's take the last step and create a reproducible operating system using NixOS to contain only the programs we want:
|
||||
|
||||
```diff
|
||||
diff --git a/flake.nix b/flake.nix
|
||||
index 99d6d52..81e98c9 100644
|
||||
--- a/flake.nix
|
||||
+++ b/flake.nix
|
||||
@@ -20,5 +26,27 @@
|
||||
'';
|
||||
};
|
||||
});
|
||||
+
|
||||
+ nixosConfigurations = let
|
||||
+ system = "x86_64-linux";
|
||||
+ in {
|
||||
+ default = nixpkgs.lib.nixosSystem {
|
||||
+ inherit system;
|
||||
+ modules = [
|
||||
+ {
|
||||
+ users.users.alice = {
|
||||
+ isNormalUser = true;
|
||||
+ # enable sudo
|
||||
+ extraGroups = ["wheel"];
|
||||
+ packages = [
|
||||
+ self.outputs.packages.${system}.default
|
||||
+ ];
|
||||
+ initialPassword = "swordfish";
|
||||
+ };
|
||||
+ system.stateVersion = "24.05";
|
||||
+ }
|
||||
+ ];
|
||||
+ };
|
||||
+ };
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now we can run this NixOS configuration as a reproducible virtual machine:
|
||||
|
||||
```console
|
||||
$ nixos-rebuild build-vm --flake .#default
|
||||
$ ./result/bin/run-nixos-vm
|
||||
|
||||
# I/O snippet from QEMU
|
||||
nixos login: alice
|
||||
Password:
|
||||
|
||||
$ readlink $(which what-is-my-ip)
|
||||
/nix/store/lr6wlz2652r35rwzc79samg77l6iqmii-what-is-my-ip/bin/what-is-my-ip
|
||||
|
||||
$ what-is-my-ip
|
||||
24.5.113.148
|
||||
```
|
||||
|
||||
💥 Hash `lr6wlz2652r35rwzc79samg77l6iqmii` present again!
|
||||
|
||||
We took a relatively simple script through a variety of applications in the Nix ecosystem: build recipe, shell, docker image, and finally NixOS VM.
|
||||
|
||||
## Flakes as All Kinds of Package Management
|
||||
|
||||
One of the super neat part about Flakes is that anywhere you find a Flake, you
|
||||
can make use of it. Try it out now!
|
||||
|
||||
> **NOTE**: The following obviously runs code from the internet. Be wary of doing this in general.
|
||||
|
||||
Run a Flake's package:
|
||||
|
||||
```console
|
||||
$ nix run git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way
|
||||
24.5.113.148
|
||||
```
|
||||
|
||||
Enter a Flake's development environment:
|
||||
|
||||
```console
|
||||
$ nix develop git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way -c $SHELL
|
||||
Hello, Nix!
|
||||
$ what-is-my-ip
|
||||
24.5.113.148
|
||||
|
||||
# want to hack on the Helix text editor?
|
||||
$ nix develop github:helix-editor/helix
|
||||
# yeah, seriously! that's it!
|
||||
```
|
||||
|
||||
Load a Flake's docker image and run it:
|
||||
|
||||
```console
|
||||
$ docker load < $(nix build git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way#container --print-out-paths)
|
||||
Loaded image: what-is-my-ip-container:wg0z43v4sc1qhq7rsqg02w80vsfk9dl0
|
||||
$ docker run -it what-is-my-ip-container:c9g6x30invdq1bjfah3w1aw5w52vkdfn
|
||||
```
|
||||
|
||||
Run a Flake's NixOS configuration as a virtual machine:
|
||||
|
||||
```console
|
||||
$ nixos-rebuild build-vm --flake git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way#default
|
||||
Done. The virtual machine can be started by running /nix/store/xswwdly9m5bwhcz9ajd6km5hx9vdmfzw-nixos-vm/bin/run-nixos-vm
|
||||
$ /nix/store/xswwdly9m5bwhcz9ajd6km5hx9vdmfzw-nixos-vm/bin/run-nixos-vm
|
||||
```
|
||||
|
||||
Run an exact replica my workstation in a virtual machine (as configured via Nix
|
||||
-- meaning it will not have my passwords on it 😉):
|
||||
|
||||
```console
|
||||
# NOTE: this will probably take a good, long time to build and lots of bandwidth
|
||||
# NOTE: you probably won't even be able to login
|
||||
$ nixos-rebuild build-vm --flake git+https://git.lyte.dev/lytedev/nix#dragon
|
||||
Done. The virtual machine can be started by running /nix/store/abc-nixos-vm/bin/run-dragon-vm
|
||||
$ /nix/store/abc-nixos-vm/bin/run-dragon-vm
|
||||
```
|
||||
|
||||
## Flakes as Inputs
|
||||
|
||||
Finally, Flakes can take any other Flake as input and use them for their own outputs. This means that they compose wonderfully. Any Flake can use anything from any other Flake. They're basically big ol' distributed functions (if you squint just right). `nixpkgs` itself is "simply" a huge Flake.
|
||||
|
||||
You can generate your own Flake however you like; Flakes provide templating
|
||||
facilities. Here, you can use my very own template:
|
||||
|
||||
```console
|
||||
$ mkdir -p ~/my-fun-flake
|
||||
$ cd ~/my-fun-flake
|
||||
$ nix flake init --template git+https://git.lyte.dev/lytedev/nix#nix-flake
|
||||
$ git init
|
||||
$ git add -A
|
||||
$ git commit -am 'initial commit'
|
||||
$ nix develop -c $SHELL
|
||||
```
|
||||
|
||||
And now you have everything you need to work on a Nix flake as if you were me!
|
||||
|
||||
To add the package from this tutorial, we can simply do this:
|
||||
|
||||
```diff
|
||||
diff --git a/flake.nix b/flake.nix
|
||||
index 408d4f2..c5d6883 100644
|
||||
--- a/flake.nix
|
||||
+++ b/flake.nix
|
||||
@@ -2,16 +2,20 @@
|
||||
inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
|
||||
inputs.pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
+ inputs.learn-flakes-the-fun-way.url = "git+https://git.lyte.dev/lytedev/learn-flakes-the-fun-way";
|
||||
+ inputs.learn-flakes-the-fun-way.inputs.nixpkgs.follows = "nixpkgs";
|
||||
+
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
pre-commit-hooks,
|
||||
+ learn-flakes-the-fun-way,
|
||||
...
|
||||
}: let
|
||||
systems = ["aarch64-linux" "aarch64-darwin" "x86_64-darwin" "x86_64-linux"];
|
||||
forSystems = nixpkgs.lib.genAttrs systems;
|
||||
in {
|
||||
formatter = genPkgs (pkgs: pkgs.alejandra);
|
||||
|
||||
@@ -26,7 +30,7 @@
|
||||
|
||||
devShells = genPkgs (pkgs: {
|
||||
nix = pkgs.mkShell {
|
||||
- packages = with pkgs; [nil alejandra];
|
||||
+ packages = with pkgs; [nil alejandra learn-flakes-the-fun-way.packages.${pkgs.system}.default];
|
||||
inherit (self.outputs.checks.${pkgs.system}.pre-commit-check) shellHook;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
Afterwards you can re-enter your development shell and run our script:
|
||||
|
||||
```console
|
||||
$ nix develop -c $SHELL
|
||||
$ what-is-my-ip
|
||||
24.5.113.148
|
||||
```
|
||||
|
||||
## Other Flakey (but in a good way) Tools
|
||||
|
||||
On top of all this, many very awesome tools can exist. You can leverage a `direnv`-style setup such that whenever you enter a directory you enter its development shell and are completely ready to work within that project with all the necessary tools with all the correct versions _reproducibly_.
|
||||
|
||||
Flakes have `checks` which are run when you run `nix flake check`. You can use this for CI to ensure that your Flake is always able to build whatever packages it needs or even check that code is formatted correctly.
|
||||
|
||||
All of this in one place makes for a pretty incredible developer experience.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Hopefully, seeing the _fun things_ you can do with Nix might inspire you to push through the hard parts.
|
||||
|
||||
There is a golden pot 💰 at the end of this rainbow 🌈 awaiting you.
|
||||
|
||||
**Learn Flakes the fun way.**
|
||||
|
||||
[0]: https://fzakaria.com/2024/07/05/learn-nix-the-fun-way.html
|
||||
|
|
@ -9,15 +9,15 @@ title: Mirroring Gitea to Other Repository Management Services (GitHub, GitLab,
|
|||
draft: false
|
||||
---
|
||||
|
||||
**NOTE**: Gitea now supports this out-of-the-box and probably fits your
|
||||
use-case: https://docs.gitea.io/en-us/repo-mirror/
|
||||
|
||||
I have a [Gitea][gitea] instance I self-host at home. I keep most of my
|
||||
repositories there, but I recognize that most other developers and potential
|
||||
employers will want to see [my work _on_ GitHub][me-on-github].
|
||||
|
||||
<!--more-->
|
||||
|
||||
**NOTE**: Gitea now supports this out-of-the-box and probably fits your
|
||||
use-case: https://docs.gitea.io/en-us/repo-mirror/
|
||||
|
||||
# TL;DR
|
||||
|
||||
- Setup an SSH key for your Gitea instance on the relevant external repositories
|
||||
|
|
17
content/blog/my-uses.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
date: "2023-07-06T14:32:00-05:00"
|
||||
title: "Things I Use"
|
||||
draft: false
|
||||
toc: false
|
||||
---
|
||||
|
||||
I saw a post recently on ye ole fediverse that [asked developers if they have
|
||||
a "uses" page](https://dev.to/nickytonline/do-you-have-a-uses-page-5b82) and
|
||||
I was inspired to create mine. I wrote up just about everything I use on a
|
||||
weekly basis.
|
||||
|
||||
<a href="/uses">You can read about all the things I use here</a>!
|
||||
|
||||
<!--more-->
|
||||
|
||||
Enjoy!
|
111
content/blog/restic-backups.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
date: "2023-07-06T14:32:00-05:00"
|
||||
title: "Backups with Restic"
|
||||
draft: true
|
||||
toc: false
|
||||
---
|
||||
|
||||
For the longest time, my backup setup has been [a script I run manually that
|
||||
was quite dumb][backupify] that had no features other than encryption. After
|
||||
getting my feet wet with `btrfs` somewhat recently and seeing the magic of
|
||||
deduplication, compression, and snapshots, I was all-in on these features and
|
||||
also wanted them for my backups.
|
||||
|
||||
<!--more-->
|
||||
|
||||
# TL;DR
|
||||
|
||||
- Install `restic` on both machines (may only be needed on the backupper?)
|
||||
- Create a `restic` user on the backuppee, setup a directory for backups with
|
||||
the right permissions, and add the backupper's public key
|
||||
- `restic -r sftp:restic@backuppee:/backups init` to setup a repo, put the
|
||||
password in a secret place accessible only to the backupper user
|
||||
- `for d in $DIRS; do RESTIC_PASSWORD_COMMAND="load secret restic-key" restic -r sftp:restic@backuppee:/backups "$d"; done`
|
||||
|
||||
# Planning
|
||||
|
||||
The most important thing to think about when it comes to backups is to think
|
||||
about what you are protecting. It's easy enough to just backup _everything_ and
|
||||
I know plenty of folks that do this! However, I'm not that type. I like to keep
|
||||
things pretty minimal, so I'll evaluate which things truly are worth backing up:
|
||||
|
||||
In my case, the only things I really want to back up are anything that might be
|
||||
considered unique data that cannot be easily reproduced such as the following:
|
||||
|
||||
- Family photos and videos
|
||||
- Secrets, keys, and anything else that provides access to other important
|
||||
assets
|
||||
- Communications and their context (emails, texts, etc.)
|
||||
- Backups of devices for restoration in case of bricking (phones and nintendo
|
||||
consoles come to mind)
|
||||
- Source code for projects
|
||||
|
||||
My current solutions for these are varied:
|
||||
|
||||
- **Family Pictures**: Google Photos
|
||||
- I would love to possibly look into a self-hosted solution, but Google Photos
|
||||
is unfortunately too easy and cheap
|
||||
- **Secrets**: These go into a combination of `password-store` and Vaultwarden.
|
||||
- The `password-store` database is backed up pretty much automatically via
|
||||
`git` to a handful of places and the data is encrypted at rest.
|
||||
- The Vaultwarden database is part of the "mission critical" server backup
|
||||
that happens manually. These backups are untested, but anything in here
|
||||
should ultimately be recoverable from redundancies in the `password-store`
|
||||
database via "forgot my password" mechanisms.
|
||||
- **Communications**: These are handled by whatever cloud services the
|
||||
communications happen over. Email is all Gmail at the moment, chats are
|
||||
varied, etc.
|
||||
- **Device Backups**: These have been simple enough. Copy the backups to the
|
||||
various backup locations and forget about them.
|
||||
- **Code**: I have pretty good `git` habits after almost 15 years of version
|
||||
control'ing, so I push code all the time to a server which in-turn backs up
|
||||
the code.
|
||||
|
||||
So where am I putting this data? I have a few larger disks here at my house, but
|
||||
I also host a sprinkling of machines at friends and family's houses out of the
|
||||
way and we share space as needed, allowing all of us to have redundant remote
|
||||
backups. That said, my machines there are not the most robust. Here are things I'm concerned about:
|
||||
|
||||
- Running out of space
|
||||
- No deduplication means this _will_ happen eventually.
|
||||
- Bitrot
|
||||
- They say it's rare, and perhaps I'm confusing disk damage with bitrot, but
|
||||
I definitely have been bit by this in some form or another. I want my backup
|
||||
system to combat this as much as possible (checksums and error correction
|
||||
via btrfs) but also to somehow regularly and automatically let me know if
|
||||
and when it occurs
|
||||
- Not automated
|
||||
- I would have a lot more peace-of-mind if I knew I could just backup
|
||||
everything nightly and not worry about it.
|
||||
|
||||
Backing up everything nightly was not an option currently, since I have ~1TB of
|
||||
data backed up and I currently just sync over everything in the local backup
|
||||
directory via rsync. I know, I've probably got the wrong flags, since rsync
|
||||
should be just fine for this, but I also wanted deduplication and a system that
|
||||
would let me pull out individual files if I wanted.
|
||||
|
||||
# Enter Restic
|
||||
|
||||
Restic pretty much seemed perfect. Seemed simple enough to setup and manage, so I gave it a shot
|
||||
|
||||
My current goals are certainly "good enough", but the lack of automation and terribly inefficient use of bandwidth with my remote hosts was _not_ ideal:
|
||||
|
||||
|
||||
|
||||
|
||||
# Setup
|
||||
|
||||
I aimed to keep things pretty secure, so I setup a user specifically for my
|
||||
backups on my backuppee devices, the machines with big hard disks (calm down)
|
||||
that would hold the backed-up data.
|
||||
|
||||
My backupper machines would be the ones pushing data, which, for now, was really
|
||||
just one box. My server which houses the actually important data.
|
||||
|
||||
All my other machines really just interface with that server to push and pull
|
||||
code and data via git. Pretty much anything else should be lose-able in my
|
||||
situation. I use Google Photos for backing up pictures, so I don't really worry
|
||||
about those for now. The only other data I want backed up is older backups
|
||||
(giant tarballs).
|
||||
|
||||
[backupify]: https://git.lyte.dev/lytedev/dotfiles/src/commit/afed5fa6bbb6bc2733a3cadcb940d6599823b59d/common/bin/backupify
|
428
content/blog/rust-vs-go.md
Normal file
|
@ -0,0 +1,428 @@
|
|||
---
|
||||
title: "Rust vs. Go"
|
||||
date: "2024-06-06"
|
||||
draft: true
|
||||
---
|
||||
|
||||
A lot of folks have been pitting Go and Rust against each other. Normally, I
|
||||
would abstain from throwing my opinion out on the internet, but it's come up at
|
||||
work and since I heavily prefer one over the other (Rust subjectively!), I want
|
||||
to write out why that is. Hopefully it is useful to others out there.
|
||||
|
||||
<!--more-->
|
||||
|
||||
# Rust versus Golang
|
||||
|
||||
I'm aiming to keep this succinct so the people I share this with will actually
|
||||
read it. This will probably be dense and pointed, as I'm trying to construct an
|
||||
argument.
|
||||
|
||||
This article is opinion and not stating objective fact. These are my
|
||||
preferences. I have roughly-equivalent Rust experience, exposure, and knowledge
|
||||
than I do Golang, though I have run, operated, and maintained far more Golang
|
||||
services at my job than I have Rust.
|
||||
|
||||
Also by posting this I'm sure I will get free <s>flame wars</s> consultation on
|
||||
my Go code and become a better programmer! 😜
|
||||
|
||||
## Stop Comparing! They're Different!
|
||||
|
||||
Of course they are! This doesn't mean they can't be used to solve the same
|
||||
problems. In my experience, one is simply better than the other for the same
|
||||
use-cases.
|
||||
|
||||
They're also the same _category_ of thing: general purpose programming
|
||||
languages. Which means it's good to talk about the various strengths and
|
||||
weaknesses of the two.
|
||||
|
||||
# Why Rust
|
||||
|
||||
## Rust has _all_ of Go's superpowers
|
||||
|
||||
Ok, except it's uber simplicity. But what you lose in simplicity, you gain in
|
||||
many other areas. I'll elaborate later.
|
||||
|
||||
### Goroutines
|
||||
|
||||
Use `tokio::spawn`. Yes, they are different (mainly stackless versus
|
||||
stackful) but the goal and considerations are effectively the same.
|
||||
|
||||
If you don't like Tokio or can't use it, there are different implementations of
|
||||
this on top of Rust's `async/await` system that have the same benefits.
|
||||
|
||||
While it's arguably not as cohesive as Go's implementation, it's there if you
|
||||
want it.
|
||||
|
||||
### Channels
|
||||
|
||||
Rust has these. See `std::sync::mpsc`.
|
||||
|
||||
### Defer
|
||||
|
||||
Largely unnecessary thanks to the compiler, but you can implement your
|
||||
own via the `Drop` trait or reach for the `scopeguard` crate.
|
||||
|
||||
### Tooling
|
||||
|
||||
Again, Rust has an incredible tools ecosystem just like Go. Better in many ways
|
||||
since many tools considered _necessary_ for modern and idiomatic Go are simply
|
||||
not for Rust, such as code generation tools. Such needs are instead fulfilled by
|
||||
metaprogramming (macros) instead.
|
||||
|
||||
### Explicit error handling
|
||||
|
||||
Haha! I guess I'm the four-millionth person to write about how awful `if err !
|
||||
= nil` is, but seriously, Rust's `?` operator and pattern matching abilities are
|
||||
so fantastic.
|
||||
|
||||
You're gonna _love_ Rust in comparison.
|
||||
|
||||
## Rust is expressive
|
||||
|
||||
Now we're getting to some of the things Rust is simply better than Go at.
|
||||
|
||||
Being expressive has downsides. A common question I'll find myself asking is
|
||||
"Which of these four ways is the _best_ way to do this?"
|
||||
|
||||
But I would rather be able to have the expressions in the code mirror the intent
|
||||
_more_ than be stuck with Go's simplicity.
|
||||
|
||||
## Rust code has fewer bugs
|
||||
|
||||
Obviously this isn't entirely a fair thing to say, but it is true that Rust
|
||||
simply makes it incredibly difficult (effectively impossible?) to have certain
|
||||
kinds of bugs in your code as opposed to Go.
|
||||
|
||||
I simply write my code in Rust and when it compiles it works as I intended.
|
||||
Rust code and the compiler make it much more difficult to represent invalid
|
||||
states.
|
||||
|
||||
Meanwhile Go code has nil checks, implicit interfaces, and a few other things
|
||||
you will inevitably run into.
|
||||
|
||||
That said, Go's tooling has been improving a _lot_ around this in my opinion;
|
||||
`golangci-lint` is awesome and shores up much of these issues.
|
||||
|
||||
## Inline tests
|
||||
|
||||
I strongly dislike being required to put my unit tests in separate files. Call
|
||||
me crazy, but having the tests in the same file is something I find appealing.
|
||||
|
||||
Also
|
||||
[doctests](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html).
|
||||
The best kind of inline test. Gotta have 'em.
|
||||
|
||||
## Modules and dependencies
|
||||
|
||||
Go's module system got bolted on a little late and suffers from the Google
|
||||
monorepo assumptions/expectations situation. In some ways, this is actually
|
||||
very cool. In many ways, though, it's pretty annoying. These days, if you follow
|
||||
the "modern" path, things can work great.
|
||||
|
||||
Rust has its own issues with Cargo and its ties to GitHub, but even those can be
|
||||
worked around if you like.
|
||||
|
||||
Overall, Rust has a more familiar and approachable module system.
|
||||
|
||||
## Making Changes
|
||||
|
||||
When you change a hunk of Go code, you can't know for sure what else in the code
|
||||
may need to change as a result. If you add a field to a struct, you may need to
|
||||
find the places you are building that struct and update the code and hopefully
|
||||
you don't miss a spot.
|
||||
|
||||
Rust makes changing an existing codebase less scary. It will tell you pretty
|
||||
much everywhere you need to go and change other things as a result of your small
|
||||
change. It totally changes the way you modify software.
|
||||
|
||||
## Documentation
|
||||
|
||||
Rust's standard library and popular crates' documentation is _generally_ much
|
||||
better than Go's. Doctests are wonderful and examples are more common in Rust
|
||||
crates than in Go modules.
|
||||
|
||||
## Macros
|
||||
|
||||
Having them is better than not. They are awesome. Yes, they make the compiler
|
||||
slower. Yes, they can do awful stuff.
|
||||
|
||||
So does Go codegen.
|
||||
|
||||
But being able to derive implementations and affect them with attributes is
|
||||
really phenomenal. Especially if you mess with something like `bevy` or `axum`.
|
||||
|
||||
## Traits
|
||||
|
||||
Rust forces you the implement a trait explicitly while Go does not. It's a small
|
||||
thing, but this again lets the code communicate the intent of the programmer.
|
||||
|
||||
## No surprises
|
||||
|
||||
Nothing that executes before `main()` while Go has freakin' `init()` and of
|
||||
course you can instantiate literally anything globally.
|
||||
|
||||
In Rust, you don't have this, which sucks and means you do `OnceLock`/
|
||||
`OnceCell`/`lazy_static!` instead and it can be painful if you're so dead set on
|
||||
doing something weird like that.
|
||||
|
||||
But I'll take no surprises and explicitness any day.
|
||||
|
||||
## Examples
|
||||
|
||||
I wanted to include a number of code examples I found particularly
|
||||
Rust-favoring. A picture is worth a thousand words!
|
||||
|
||||
### Obvious: Error Handling
|
||||
|
||||
This is a simplified function in production right now:
|
||||
|
||||
```go
|
||||
func (t *Bot) FinishReceivingRequest(req RequestInProgress) (result *Request, err error) {
|
||||
result.Data = req.Data
|
||||
|
||||
assignee, err := t.GetAssigneeSlackUser(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve assignee's Slack user info: %w", err)
|
||||
}
|
||||
result.SlackAssigneeID = assignee.ID
|
||||
|
||||
manager, err := t.Slack.GetUserManager(assignee.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve the assignee's manager Slack user info: %w", err)
|
||||
}
|
||||
result.SlackAssigneeMgrID = manager.ID
|
||||
|
||||
issueSkeleton, err := req.Data.BuildJiraIssue(t, req)
|
||||
if err != nil {
|
||||
return nil, logrus.Errorf("Failed to create Jira issue object: %+v", err)
|
||||
}
|
||||
|
||||
issue, res, err := t.Jira.CreateIssue(issueSkeleton)
|
||||
defer res.Body.Close()
|
||||
if err != nil {
|
||||
_, _, err := t.Slack.PostMessage(t.Conf.Slack.Channels.RequestThreads,
|
||||
slack.MsgOptionDisableLinkUnfurl(),
|
||||
slack.MsgOptionBlocks(
|
||||
slack.NewSectionBlock(
|
||||
&slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "I failed to create a Jira ticket. See logs for details.\n\nError: " + err.Error(),
|
||||
}, nil, nil,
|
||||
),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to send Jira ticket creation failed message to slack: %v", err)
|
||||
}
|
||||
}
|
||||
result.JiraIssueKey = &issue.Key
|
||||
logrus.Infof("Jira issue created: %s", result.JiraIssueKey)
|
||||
|
||||
channelId, ts, err := t.Slack.PostMessage(
|
||||
t.Conf.Slack.Channels.RequestThreads,
|
||||
slack.MsgOptionDisableLinkUnfurl(),
|
||||
req.MainMessageBlocks(),
|
||||
)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to post Slack message for request -- this means there is data that has been created that nobody knows about: %w", err)
|
||||
}
|
||||
result.SlackTimeStamp = ts
|
||||
result.SlackChannelId = channelId
|
||||
|
||||
// fetch permalink to the posted message to update jira ticket with
|
||||
url, err := t.Slack.GetPermalink(&slack.PermalinkParameters{
|
||||
Channel: channelId,
|
||||
Ts: ts,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: retry?
|
||||
logrus.Errorf("Failed to retrieve permalink for Slack message for request -- this means we could not link the jira ticket to the slack thread: %s", err)
|
||||
}
|
||||
req.SlackThreadURL = url
|
||||
|
||||
if issue != nil {
|
||||
rl := jira.RemoteLink{
|
||||
Object: &jira.RemoteLinkObject{
|
||||
URL: url,
|
||||
Title: "Slack Thread",
|
||||
},
|
||||
}
|
||||
_, res, err := t.Jira.AddIssueRemoteLink(issue.ID, &rl)
|
||||
defer res.Body.Close()
|
||||
if err != nil {
|
||||
// TODO: retry?
|
||||
logrus.Errorf("Failed to add remote link to Slack message thread (ts: '%s', link: '%s') for jira issue (key: '%s'): %s", ts, url, issue.Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Infof("Request data: %+v", req)
|
||||
logrus.Tracef("request's db data json: %+v", req.DataJSON)
|
||||
logrus.Tracef("request's db data: %+v", req.Data)
|
||||
|
||||
dbResult := t.DB.Create(req)
|
||||
logrus.Tracef("request db create result: %+v", dbResult)
|
||||
|
||||
// it's not the end of the world if we cannot update the main message, so we try it, log it, and keep going
|
||||
blocks, err = req.MainMessageBlocks()
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to build slack blocks for original message update: %v", err)
|
||||
}
|
||||
|
||||
_, _, _, err = t.Slack.UpdateMessage(
|
||||
t.Conf.Slack.Channels.RequestThreads,
|
||||
req.SlackTimeStamp,
|
||||
blocks,
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to update original message: %v", err)
|
||||
}
|
||||
|
||||
|
||||
_, _, err = t.Slack.PostMessage(
|
||||
channelId,
|
||||
slack.MsgOptionTS(ts),
|
||||
slack.MsgOptionBlocks(req.FirstReplyMessageBlocks()...),
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to post follow-up message in thread: %s", err)
|
||||
}
|
||||
|
||||
if req.Data.NeedsApproval() {
|
||||
req, err = t.ApprovalSetup(req)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to setup approval: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
Here's how it looks in my Rust dream rewrite (thanks `color_eyre::Result`):
|
||||
|
||||
```rust
|
||||
// fetch permalink to the posted message to update jira ticket with
|
||||
url, err := t.Slack.GetPermalink(&slack.PermalinkParameters{
|
||||
Channel: channelId,
|
||||
Ts: ts,
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: retry?
|
||||
logrus.Errorf("Failed to retrieve permalink for Slack message for request -- this means we could not link the jira ticket to the slack thread: %s", err)
|
||||
}
|
||||
req.SlackThreadURL = url
|
||||
|
||||
if issue != nil {
|
||||
rl := jira.RemoteLink{
|
||||
Object: &jira.RemoteLinkObject{
|
||||
URL: url,
|
||||
Title: "Slack Thread",
|
||||
},
|
||||
}
|
||||
_, res, err := t.Jira.AddIssueRemoteLink(issue.ID, &rl)
|
||||
defer res.Body.Close()
|
||||
if err != nil {
|
||||
// TODO: retry?
|
||||
logrus.Errorf("Failed to add remote link to Slack message thread (ts: '%s', link: '%s') for jira issue (key: '%s'): %s", ts, url, issue.Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Infof("Request data: %+v", req)
|
||||
logrus.Tracef("request's db data json: %+v", req.DataJSON)
|
||||
logrus.Tracef("request's db data: %+v", req.Data)
|
||||
|
||||
dbResult := t.DB.Create(req)
|
||||
logrus.Tracef("request db create result: %+v", dbResult)
|
||||
|
||||
// it's not the end of the world if we cannot update the main message, so we try it, log it, and keep going
|
||||
blocks, err = req.MainMessageBlocks()
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to build slack blocks for original message update: %v", err)
|
||||
}
|
||||
|
||||
_, _, _, err = t.Slack.UpdateMessage(
|
||||
t.Conf.Slack.Channels.RequestThreads,
|
||||
req.SlackTimeStamp,
|
||||
blocks,
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to update original message: %v", err)
|
||||
}
|
||||
|
||||
|
||||
_, _, err = t.Slack.PostMessage(
|
||||
channelId,
|
||||
slack.MsgOptionTS(ts),
|
||||
slack.MsgOptionBlocks(req.FirstReplyMessageBlocks()...),
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to post follow-up message in thread: %s", err)
|
||||
}
|
||||
|
||||
if req.Data.NeedsApproval() {
|
||||
req, err = t.ApprovalSetup(req)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to setup approval: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
# Why Not Rust
|
||||
|
||||
While I've made it clear that I prefer Rust, I also recognize that there are
|
||||
things that I consider Go to handle better.
|
||||
|
||||
## Standard libraries
|
||||
|
||||
Go has a big standard library and just includes a ton of stuff. I wish Rust had
|
||||
something similar for "blessed" "core" crates. I understand the standard library
|
||||
for a systems-level language must be small, but that's how I feel.
|
||||
|
||||
### Other Builtins
|
||||
|
||||
Go can kind of manage its own versions and everything and you pretty much just
|
||||
do `go whatever` and get on with your life.
|
||||
|
||||
With Rust, you need to learn a couple different tools (but mainly just `cargo`)
|
||||
such as `rustup`.
|
||||
|
||||
## Go is Simple
|
||||
|
||||
It's easy to get caught up trying to do things in Rust the "right" way and work
|
||||
with the borrow checker and think about the optimal solution -- to revel in the
|
||||
computer science puzzle that the compiler presents to you.
|
||||
|
||||
In Go, that is simply less true. You will do the obvious thing at perhaps some
|
||||
slight performance cost and get back to implementing your feature and shipping
|
||||
code. It's almost always going to be perfectly sufficient and generally easy to
|
||||
come back to and understand.
|
||||
|
||||
## Go Compilation Speed
|
||||
|
||||
The Go compiler is wonderfully quick. In my experience, though, there are
|
||||
usually codegen tools you end up adopting that offset this speed and slow things
|
||||
down again that require extra vigilance and tending to keep things fast.
|
||||
|
||||
With Rust, you have one compiler speed: not so fast. It's still sufficiently
|
||||
quick for me, especially on a Ryzen 9 5950X or an Apple M1 Max. For most
|
||||
projects, it does a good job being incremental, too.
|
||||
|
||||
But you will build Rust
|
||||
|
||||
## Build Tinkering
|
||||
|
||||
With Go, you're just gonna build the binary and run it.
|
||||
|
||||
With Rust, you _do_ have to think about `--release` performance -- such as when
|
||||
writing Leetcode solutions against your friends.
|
||||
|
||||
I'm getting nitpicky here, but... it's my article.
|
||||
|
||||
## Versioning
|
||||
|
||||
Rust has issues with MSRV "promises" being largely unenforceable due to Cargo
|
||||
lockfiles not being `--locked` by default. This is pretty mind-boggling to me.
|
||||
Could also be a skill issue.
|
|
@ -9,6 +9,8 @@ create an anchor with that name and reference it later with `*{NAME_HERE}` (like
|
|||
`*key`) to avoid repeating yourself in a document. You can also "merge" object
|
||||
values using the `<<` key.
|
||||
|
||||
<!--more-->
|
||||
|
||||
# Introduction
|
||||
|
||||
Since working at [Postmates][pm] ([we're hiring][pm-referral]!) and getting my
|
||||
|
|
|
@ -23,13 +23,13 @@ https://git.faceless.lyte.dev/lytedev/weechat-matrix-encryption-guide.git
|
|||
/tmp/wmeg && $EDITOR /tmp/wmeg/easy-script.bash && /tmp/wmeg/easy-script.bash`
|
||||
+ [Configure](#configuration) as needed
|
||||
|
||||
## Python Versions
|
||||
# Python Versions
|
||||
|
||||
We need to establish which version of Python your WeeChat is using. You can find
|
||||
this out in WeeChat with `/python version`. In my case, my `python` binary is
|
||||
3.7.2 (`python -V`) while my WeeChat Python version is 2.7.15.
|
||||
|
||||
## Dependencies
|
||||
# Dependencies
|
||||
|
||||
There are a number of dependencies we can go ahead and start grabbing. The main
|
||||
repository lists a number of them in the `README`, so we will grab those. We
|
||||
|
@ -44,7 +44,7 @@ Notice that we left out the [`matrix-nio`][matrix-nio] dependency. It's not in
|
|||
PyPi, so we can't just `pip2 install matrix-nio` (yet!) and PyPi's `nio` package
|
||||
is something probably unrelated, so we'll need to install it manually.
|
||||
|
||||
## Installing `matrix-nio`
|
||||
# Installing `matrix-nio`
|
||||
|
||||
Let's go ahead and clone down the repository and get ready to do some stuff:
|
||||
|
||||
|
@ -98,7 +98,7 @@ package:
|
|||
sudo python2 ./setup.py install
|
||||
```
|
||||
|
||||
## Weechat Plugin Installation
|
||||
# Weechat Plugin Installation
|
||||
|
||||
Once we've done that, we should have all the dependencies for `weechat-matrix`,
|
||||
so let's go ahead and clone that and install it!
|
||||
|
@ -111,7 +111,7 @@ make install
|
|||
|
||||
Done!
|
||||
|
||||
## Configuration
|
||||
# Configuration
|
||||
|
||||
The rest is up to you! You'll need to [configure your Matrix servers within
|
||||
WeeChat][weechat-matrix-config] and then verify keys. Verifying keys isn't
|
||||
|
|
|
@ -11,6 +11,8 @@ a long time and forgotten what all is necessary in order to spin up
|
|||
a development environment or which incantation is needed to get this particular
|
||||
project to compile, you need a Makefile.
|
||||
|
||||
<!--more-->
|
||||
|
||||
# Introduction
|
||||
|
||||
|
||||
|
|
|
@ -6,15 +6,13 @@ imageOverlayOpacity: 0.7
|
|||
heroBackgroundColor: "#333"
|
||||
title: Contact
|
||||
description: "Need to get in touch?"
|
||||
toc: false
|
||||
---
|
||||
|
||||
If you want to reach out, please do so via my email at [daniel@lyte.dev][e],
|
||||
[join my Discord server][d], [mesage me on the Matrix fediverse][m], or
|
||||
`@lytedev` across the usual IRC networks.
|
||||
|
||||
There was once a contact form here, but spam and script kiddies ruin the fun
|
||||
parts of the internet and I wouldn't have it any other way. ❤️
|
||||
|
||||
[e]: mailto:daniel@lyte.dev
|
||||
[d]: https://discord.gg/jUCXCYp
|
||||
[m]: https://matrix.to/#/@lytedev:matrix.org
|
||||
|
|
90
content/minecraft-server-status.md
Normal file
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
title: Minecraft Server Status Checker
|
||||
toc: false
|
||||
aliases:
|
||||
/ourcraft-status
|
||||
---
|
||||
|
||||
<noscript>
|
||||
<p>
|
||||
It looks like you have javascript disabled! This page uses javascript to query a third-party server which fetches data from our minecraft servers.
|
||||
</p>
|
||||
</noscript>
|
||||
|
||||
<button id="check-minecraft-server-status">Reload</button>
|
||||
|
||||
<div id="server-status">
|
||||
<p id="server-status-loading">
|
||||
Checking minecraft servers' status...
|
||||
</p>
|
||||
<ul id="servers">
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function checkMinecraftServerStatus() {
|
||||
const loading = document.getElementById("server-status-loading")
|
||||
const servers = document.getElementById("servers")
|
||||
|
||||
loading.style.display = "inherit";
|
||||
servers.style.display = "none";
|
||||
try {
|
||||
fetch("https://deno-deploy-private.deno.dev/minecraft-server-status").then(res => {
|
||||
res.json().then(statuses => {
|
||||
console.log(statuses)
|
||||
loading.style.display = "none";
|
||||
servers.style.display = "inherit";
|
||||
const newChildren = [];
|
||||
for (let k of Object.keys(statuses)) {
|
||||
let pl = null;
|
||||
const status = statuses[k]
|
||||
const el = document.createElement("li")
|
||||
const s = document.createElement("strong")
|
||||
s.textContent = k
|
||||
const c = document.createElement("code")
|
||||
c.className = "chroma"
|
||||
const cs = document.createElement("span")
|
||||
c.appendChild(cs)
|
||||
cs.className = (status.online ? "na" : "err")
|
||||
cs.textContent = status.online ? "ONLINE" : "OFFLINE"
|
||||
const p = document.createElement("span")
|
||||
if (status.players) {
|
||||
p.textContent = ` with ${status.players.online}/${status.players.max} players`
|
||||
if (status.players.online > 0 && status.players.list) {
|
||||
pl = document.createElement("details")
|
||||
pl.style.marginTop = "0"
|
||||
const pld = document.createElement("summary")
|
||||
pld.textContent = "Players List"
|
||||
pld.style.color = "var(--link-fg)"
|
||||
pld.style.textDecoration = "underline"
|
||||
const plist = document.createElement("ul")
|
||||
pl.appendChild(pld)
|
||||
pl.appendChild(plist)
|
||||
for (let p of status.players.list) {
|
||||
const pi = document.createElement("li")
|
||||
pi.textContent = p.name
|
||||
plist.appendChild(pi)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p.textContent = ` with ??/?? players`
|
||||
}
|
||||
el.replaceChildren(
|
||||
s,
|
||||
document.createTextNode(": "),
|
||||
c,
|
||||
p,
|
||||
)
|
||||
if (pl != null) el.appendChild(pl)
|
||||
newChildren.push(el)
|
||||
}
|
||||
servers.replaceChildren(...newChildren)
|
||||
})
|
||||
}).catch(err => console.error(err))
|
||||
} catch (err) {
|
||||
console.err(err)
|
||||
}
|
||||
}
|
||||
document.getElementById("check-minecraft-server-status").addEventListener("click", checkMinecraftServerStatus)
|
||||
document.addEventListener("DOMContentLoaded", checkMinecraftServerStatus)
|
||||
</script>
|
|
@ -1,96 +0,0 @@
|
|||
---
|
||||
title: Ourcraft Server Status Checker
|
||||
---
|
||||
|
||||
<p id="server-status-loading">
|
||||
Checking <code>ourcraft.lyte.dev</code> server status...
|
||||
</p>
|
||||
|
||||
<div id="server-status-check-failed">
|
||||
<p>
|
||||
Failed to retrieve <code>ourcraft.lyte.dev</code> server status. Unfortunately, that usually means we're <code><span style="color: var(--syntax-mag);">OFFLINE</span></code>.
|
||||
</p>
|
||||
<p>
|
||||
Please yell at <code>@lytedev</code> in the Ourcraft Discord to fix it!
|
||||
</p>
|
||||
<p><button id="server-status-check-button">Try Again</button></p>
|
||||
</div>
|
||||
|
||||
<div id="server-status-online" style="display: none;">
|
||||
<p>Server <code>ourcraft.lyte.dev</code> is <code><span style="color: var(--syntax-name);">ONLINE</span></code></p>
|
||||
<p id="server-status-player-info" style="display: none;">There are currently <span id="server-status-current-num-players">0</span>/<span id="server-status-max-players">20</span> players online.</p>
|
||||
<h3 id="server-status-player-sampling-header" style="display: none;">Online Players</h3>
|
||||
<ul id="server-status-player-sampling" style="display: none;">
|
||||
<li>Player</li>
|
||||
</ul>
|
||||
<p id="server-status-player-sampling-insufficient" style="display: none">... and more!</p>
|
||||
</div>
|
||||
|
||||
<p id="server-status-offline" style="display: none;">
|
||||
Server is <code><span style="color: var(--syntax-mag);">OFFLINE</span></code>
|
||||
</p>
|
||||
|
||||
<script src="https://mcapi.us/scripts/minecraft.min.js"></script>
|
||||
<script>
|
||||
function showServerStatus(key) {
|
||||
for (const name of ['server-status-check-failed', 'server-status-online', 'server-status-offline', 'server-status-loading']) {
|
||||
if (name == key) {
|
||||
document.getElementById(name).style.display = 'block'
|
||||
} else {
|
||||
document.getElementById(name).style.display = 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
function checkServerStatus() {
|
||||
showServerStatus('server-status-loading')
|
||||
MinecraftAPI.getServerStatus('ourcraft.lyte.dev', {port: 25565}, function (err, status) {
|
||||
console.debug("Status Errors:", err || false)
|
||||
if (err || !('online' in status)) {
|
||||
showServerStatus('server-status-check-failed')
|
||||
} else {
|
||||
handleOnlineStatus(status)
|
||||
}
|
||||
});
|
||||
}
|
||||
function handleOnlineStatus(status) {
|
||||
console.debug("Status Data:", status)
|
||||
if (status.online === true) {
|
||||
showServerStatus('server-status-online')
|
||||
const info = document.getElementById('server-status-player-info')
|
||||
const samplingHeader = document.getElementById('server-status-player-sampling-header')
|
||||
const samplingList = document.getElementById('server-status-player-sampling')
|
||||
const insufficient = document.getElementById('server-status-player-sampling-insufficient')
|
||||
|
||||
info.style.display = 'none'
|
||||
samplingHeader.style.display = 'none'
|
||||
samplingList.style.display = 'none'
|
||||
insufficient.style.display = 'none'
|
||||
|
||||
if ('players' in status) {
|
||||
if ('max' in status.players && 'now' in status.players) {
|
||||
info.style.display = 'block'
|
||||
document.getElementById('server-status-current-num-players').textContent = status.players.now
|
||||
document.getElementById('server-status-max-players').textContent = status.players.max
|
||||
if ('sample' in status.players && status.players.now > 0) {
|
||||
samplingHeader.style.display = 'block'
|
||||
samplingList.style.display = 'block'
|
||||
samplingList.textContent = ''
|
||||
for (const player of status.players.sample) {
|
||||
const newListItem = document.createElement('li')
|
||||
newListItem.textContent = player.name
|
||||
newListItem.title = player.id
|
||||
samplingList.appendChild(newListItem)
|
||||
}
|
||||
if (status.players.sample.length != status.players.now) {
|
||||
document.getElementById('server-status-player-sampling-insufficient').style.display = 'block'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showServerStatus('server-status-offline')
|
||||
}
|
||||
}
|
||||
document.getElementById("server-status-check-button").addEventListener("click", checkServerStatus)
|
||||
checkServerStatus()
|
||||
</script>
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
title: Privacy Policy
|
||||
toc: false
|
||||
---
|
||||
|
||||
I collect basic analytics to a system I run and control for insight into how
|
||||
|
|
164
content/salary.md
Normal file
|
@ -0,0 +1,164 @@
|
|||
---
|
||||
# date: 2017-02-22T14:43:02-06:00
|
||||
title: Salary Transparency
|
||||
description: "My salary information made public to help combat pay discrimination."
|
||||
toc: false
|
||||
---
|
||||
|
||||
Below is a list of jobs and salaries I've had. Dollar amounts are annual
|
||||
estimates in USD. Stock is generally sold as soon as legally possible for
|
||||
simplicity and personal policy. Dates are rough estimates in many cases.
|
||||
|
||||
## Why?
|
||||
|
||||
- Combat pay discrimination
|
||||
- Help fellow employees negotiate with employers
|
||||
- Reduce stigma around talking about personal finances
|
||||
|
||||
## Jobs
|
||||
|
||||
<section class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th>Title</th>
|
||||
<th>Start Date</th>
|
||||
<th>End Date</th>
|
||||
<th>Salary</th>
|
||||
<th>Annual Bonus Target</th>
|
||||
<th>Annual Stock Value</th>
|
||||
<th>Departure Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Bill</td>
|
||||
<td>Sr. Staff <abbr title="Software Engineer">SWE</abbr></td>
|
||||
<td>2024-08-30</td>
|
||||
<td>Still here!</td>
|
||||
<td>$217,350</td>
|
||||
<td>$22,000</td>
|
||||
<td>$130,000</td>
|
||||
<td>N/A</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Bill</td>
|
||||
<td>Sr. Staff <abbr title="Software Engineer">SWE</abbr></td>
|
||||
<td>2023-08-20</td>
|
||||
<td>2024-08-30</td>
|
||||
<td>$210,000</td>
|
||||
<td>$21,000</td>
|
||||
<td>$120,000</td>
|
||||
<td>Raise</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Bill</td>
|
||||
<td>Staff <abbr title="Software Engineer">SWE</abbr></td>
|
||||
<td>2021-06-05</td>
|
||||
<td>2023-08-20</td>
|
||||
<td>$192,258</td>
|
||||
<td>$19,226</td>
|
||||
<td>$90,000</td>
|
||||
<td>Promoted</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Divvy</td>
|
||||
<td>Staff <abbr title="Software Engineer">SWE</abbr></td>
|
||||
<td>2021-03-05</td>
|
||||
<td>2023-06-01</td>
|
||||
<td>$175,000</td>
|
||||
<td>$8,000</td>
|
||||
<td>$70,000</td>
|
||||
<td>Acquired by Bill, Raises</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Uber</td>
|
||||
<td><abbr title="Software Engineer">SWE</abbr> II</td>
|
||||
<td>2020-12-01</td>
|
||||
<td>2021-03-05</td>
|
||||
<td>$165,000</td>
|
||||
<td>$0</td>
|
||||
<td>$90,000</td>
|
||||
<td>Quit</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Postmates</td>
|
||||
<td><abbr title="Software Engineer">SWE</abbr> II</td>
|
||||
<td>2019-06-10</td>
|
||||
<td>2020-12-01</td>
|
||||
<td>$165,000</td>
|
||||
<td>$0</td>
|
||||
<td>$90,000</td>
|
||||
<td>Acquired by Uber</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Mixon Hill</td>
|
||||
<td>Systems Engineer</td>
|
||||
<td>2017-12-01</td>
|
||||
<td>2019-06-10</td>
|
||||
<td>$60,000</td>
|
||||
<td>$5,000</td>
|
||||
<td>N/A</td>
|
||||
<td>Quit</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Toggl</td>
|
||||
<td><abbr title="Software Engineer">SWE</abbr> II</td>
|
||||
<td>2017-04-01</td>
|
||||
<td>2019-07-01</td>
|
||||
<td>$55,000</td>
|
||||
<td>$0</td>
|
||||
<td>$0</td>
|
||||
<td>Fired</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Emfluence</td>
|
||||
<td><abbr title="Software Engineer">SWE</abbr> II</td>
|
||||
<td>2014-04-01</td>
|
||||
<td>2015-05-01</td>
|
||||
<td>$60,000</td>
|
||||
<td>$0</td>
|
||||
<td>$0</td>
|
||||
<td>Fired</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Voxa Ventures</td>
|
||||
<td><abbr title="Software Engineer">SWE</abbr></td>
|
||||
<td>2013-05-01</td>
|
||||
<td>2015-03-01</td>
|
||||
<td>$55,000</td>
|
||||
<td>$0</td>
|
||||
<td>$0</td>
|
||||
<td>Company Bankrupt</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Waterway Gas & Wash</td>
|
||||
<td>Line Associate</td>
|
||||
<td>2012-05-01</td>
|
||||
<td>2013-05-01</td>
|
||||
<td>$30,000</td>
|
||||
<td>$0</td>
|
||||
<td>$0</td>
|
||||
<td>Quit</td>
|
||||
<tr/>
|
||||
<tr>
|
||||
<td>Price Chopper</td>
|
||||
<td>Cashier</td>
|
||||
<td>2011-04-01</td>
|
||||
<td>2012-04-01</td>
|
||||
<td>$23,000</td>
|
||||
<td>$0</td>
|
||||
<td>$0</td>
|
||||
<td>Quit</td>
|
||||
<tr/>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
## Micro-Rant on Titles
|
||||
|
||||
Also, if we talked about how meaningless "levels" are in titles, just know
|
||||
that... they are. They're really just there to reflect that employee's pay.
|
||||
Don't judge by titles.
|
||||
|
655
content/uses.md
Normal file
|
@ -0,0 +1,655 @@
|
|||
---
|
||||
# date: 2017-02-22T14:43:02-06:00
|
||||
# image: /img/space.jpg
|
||||
# imageOverlayColor: "#000"
|
||||
# imageOverlayOpacity: 0.6
|
||||
# heroBackgroundColor: "#0af"
|
||||
title: Uses
|
||||
description: "About my tools, workflow, and other things I use as a software developer."
|
||||
---
|
||||
|
||||
This page lists the tools (both physical and otherwise) that I use to do my job
|
||||
as a software developer along with some thoughts on them.
|
||||
|
||||
While this page is likely to be out of date when you're reading it, since I am
|
||||
usually trying a few small changes here and there at any given point to try and
|
||||
improve things, I try to update it regularly. You can follow those updates by
|
||||
looking at [the history of the source code for this page
|
||||
specifically][uses-history].
|
||||
|
||||
For other pages like this from other folks, check out this repository:
|
||||
https://github.com/wesbos/awesome-uses
|
||||
|
||||
I'll go through a theoretical "day in the life" of myself working. Mostly to
|
||||
help me remember all the stuff involved, but also as a means of storytelling and
|
||||
being imformative. I normally don't drop brand names, but since that's kind of
|
||||
the point here, I will be doing a _lot_ of name dropping. No links to products
|
||||
for simplicity, but everything listed here should be searchable. If not, let
|
||||
me know! I'll try to link to anything free, though, such as software.
|
||||
|
||||
I'll break stuff up by topic as things come up so you can skip things that are
|
||||
not interesting to you. There is also a Table of Contents at the top to help
|
||||
you navigate.
|
||||
|
||||
I also think that in general sharing this much information about yourself isn't
|
||||
the _best_ idea. However, since I'm confident the bots can't know much more
|
||||
about me that they already do and this will really only mostly be useful to my
|
||||
fellow human beings, I think it's worth sharing. I hope you discover some cool
|
||||
new stuff! Better yet, I hope you recommend me better stuff! I'm always wanting
|
||||
to try new tools and discover something new that's good at something.
|
||||
|
||||
Regarding the configuration of my machines and the software referenced below,
|
||||
[please refer to my Nix repo](https://git.lyte.dev/lytedev/nix)! It may also
|
||||
be useful to look through my old [dotfiles
|
||||
repo](https://git.lyte.dev/lytedev/dotfiles).
|
||||
|
||||
# Good morning!
|
||||
|
||||
I wake up when my kids do out of a Purple mattress. I slip on my PineTime wrist
|
||||
watch, grab my flashlight, Android smartphone and backpack, put on my prescription glasses,
|
||||
and usually make some tea.
|
||||
|
||||
## Mattress: Purple King Size
|
||||
|
||||
Sleep is real important, so get a good mattress! Of course, "good" here is
|
||||
highly subjective, so you will want to do your own research. We usually have a
|
||||
kid or two join my wife and I in the mattress, so we went with a king size to
|
||||
account for this. Fantastic decision!
|
||||
|
||||
## Smart Watch: PineTime
|
||||
|
||||
I love my PineTime! It serves as a good flashlight in the pitch black of a
|
||||
baby's room and can tell the time. That alone is good enough. However, it can
|
||||
also vibrate when I get notifications on my phone if I want, which I do use
|
||||
on occasion. The price is also unbeatable at 25USD and the InfiniTime firmware
|
||||
keeps improving! I get about two weeks of battery with light use and bluetooth
|
||||
off. I get about 5 days if I've got notifications on full blast, but they
|
||||
recently improved the firmware and claim this may now be more than double!
|
||||
|
||||
## Flashlight: Emisar DW4
|
||||
|
||||
I have a phone with a flashlight. And even my watch can be enough of a
|
||||
flashlight to navigate in pitch black, but I've taken to carrying an actual
|
||||
flashlight. Specifically, an Emisar DW4. It has a magnetic tailcap so it can
|
||||
attach near or directly to many work surfaces. It can get hilariously bright or
|
||||
dim enough to be suitable for use around sleeping family members in the dark.
|
||||
|
||||
And it has fun RGB LEDs that can flash, show you the battery level, and just
|
||||
look cool. It's not a game-changer, but at times it is incredibly convenient to
|
||||
have on hand.
|
||||
|
||||
## Smart Phone: ASUS Zenfone 10
|
||||
|
||||
I've enjoyed ASUS's phones and have previously used the ROG Phone 5S. I bought
|
||||
this since it maintained most of the important features of the ROG Phone while
|
||||
being cheaper and my old ROG Phone started having bluetooth and phone call
|
||||
issues. To be fair, I bought the international Chinese version off ebay to try
|
||||
and save a buck.
|
||||
|
||||
The Zenfone 10 does everything I need. Lots of battery life, nice display for
|
||||
reading on, good speakers, blah blah blah. Phone's get less interesting all the
|
||||
time and most of them are good enough these days.
|
||||
|
||||
I hope a real Linux phone comes around!
|
||||
|
||||
### Android-Specific Software & Applications
|
||||
|
||||
Since I'm discussing my phone, I'll go over phone-specific apps (and some common
|
||||
with my laptops/desktops) now in no particular order. I have no idea if any of
|
||||
these have iOS equivalents, but here ya go.
|
||||
|
||||
- [Firefox](https://github.com/mozilla-mobile/fenix) as my web browser
|
||||
- Firefox supports (some) extensions even on Android! I use the following:
|
||||
- [Dark Reader](https://addons.mozilla.org/en-US/firefox/addon/darkreader/) for keeping things easy on my eyes
|
||||
- [uBlock Origin](https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/) for blocking ads
|
||||
- [F-Droid](https://f-droid.org) as an awesome resource for applications
|
||||
- [Termux](https://github.com/termux/termux-app) for doing Linux-y and
|
||||
terminal-y things on my phone
|
||||
- There's also [Nix-on-Droid](https://github.com/nix-community/nix-on-droid)
|
||||
which I've started using more and more
|
||||
- [OpenKeychain](https://github.com/open-keychain/open-keychain) for mobile GPG
|
||||
key management
|
||||
- [Password Store](https://github.com/android-password-store/Android-Password-Store)
|
||||
for interacting with my password manager database
|
||||
- [Bitwarden](https://github.com/bitwarden/mobile) for interacting with shared
|
||||
password databases
|
||||
- [Magisk](https://github.com/topjohnwu/Magisk) for managing Android root access
|
||||
- [NewPipe](https://github.com/TeamNewPipe/NewPipe) for YouTube access without
|
||||
dealing with ads
|
||||
- [Smart AudioBook Player](https://play.google.com/store/apps/details?id=ak.alizandro.smartaudiobookplayer)
|
||||
for listening to audiobooks
|
||||
- [Gadgetbridge](https://github.com/Freeyourgadget/Gadgetbridge) for interfacing
|
||||
with my smart watch
|
||||
- [Obsidian](https://play.google.com/store/apps/details?id=md.obsidian) for
|
||||
reading and writing my notes (sync'd via `git`)
|
||||
- [Fedilab](https://codeberg.org/tom79/Fedilab) as my mobile fediverse client
|
||||
- [Goguma](https://git.sr.ht/~emersion/goguma) as my mobile IRC client (connected to my IRC bouncer, (Soju)[https://git.sr.ht/~emersion/soju])
|
||||
- [Tailscale](https://github.com/tailscale/tailscale-android) for my VPN
|
||||
- Google Wallet for NFC payments (tap-to-pay or contactless) because getting
|
||||
cards out of a wallet is _so_ pre-COVID
|
||||
- Google Messages for SMS, MMS, and RCS
|
||||
- Google Maps for meatspace navigation
|
||||
- Pocket Casts for listening to podcasts
|
||||
- Spotify for listening to music
|
||||
|
||||
## Backpack
|
||||
|
||||
The portable office! I keep my laptop, a multi-tool, extension cord, power
|
||||
strip, laptop charger, a suite of adapters and flash drives, water bottle, and
|
||||
the odd cable here or there. If I'm feelin' that I might be gamin', I throw in
|
||||
the Steam Deck. More on all this later.
|
||||
|
||||
# Server Room
|
||||
|
||||
Once the day has started and I've said my goodbyes to the fam, I head downstairs
|
||||
to my basement where my home office is located. I walk past a super overkill
|
||||
server rack I got on Craigslist. It holds a few things, but the most important
|
||||
things are my home router/gateway. It's connected to a Google Fiber jack where
|
||||
I get 500 Mb/sec (symmetric) speeds for about 55USD/month. It's hooked up to a
|
||||
Netgear 16-port gigabit switch which in turn is hooked up to a bunch of little
|
||||
devices, the most important of which are my home server, WiFi access point, and
|
||||
a really long cable that goes to my desk where there is another tp-link 8-port
|
||||
gigabit switch.
|
||||
|
||||
The rack also has a bunch of loose cables, peripherals, and other random gear,
|
||||
like a big knife. I think I was using it to strip some wires. I should get some
|
||||
easy-mode wire strippers.
|
||||
|
||||
## Router & Gateway: Any decent dual-NIC machine
|
||||
|
||||
For a long time, I used a Raspberry Pi 4 with a USB3 ethernet adapter. It did
|
||||
great, too! But then I wanted it (and my ethernet adapter) for another project
|
||||
and I scored a Datto Alto 2 with 2 NICs built right in on Ebay for like $30.
|
||||
_Ebay is awesome_.
|
||||
|
||||
The Datto Alto 2 is great, but not because of what it is. Any little dual-NIC
|
||||
box will do nicely. It's running Arch Linux and is configured via the contents
|
||||
of [this repo](https://git.lyte.dev/lytedev/router) (please be nice to my
|
||||
network). Having a router I'm in full control of has helped with networking
|
||||
problems immensely and makes port forwarding stuff a breeze. I'm roughly
|
||||
familiar with its workings, which makes troubleshooting network problems that
|
||||
much easier for me.
|
||||
|
||||
This is the last bastion for Arch Linux in my network and I'm excited to move.
|
||||
Not because I hate Arch, but I'm _really_ loving NixOS.
|
||||
|
||||
## WiFi Access Point: Unifi AP-Pro
|
||||
|
||||
Fantastic access point that plays nicely with my very DIY home router. Not
|
||||
really much else to say. I set it up a long time ago and update it with some
|
||||
regularity, but it just works. At some point, I'd love to get wireless devices
|
||||
on their own VLAN for another layer of security.
|
||||
|
||||
## Ethernet Switches: Anything with enough speed and ports
|
||||
|
||||
Seriously I just bought the cheapest switches at Micro Center with enough ports
|
||||
for me. They're getting hilariously cheap, which is great! Having extra ports
|
||||
for ad-hoc stuff, like LAN parties, is a must-have for me, even in the age of
|
||||
WiFi.
|
||||
|
||||
_Share the load_.
|
||||
|
||||
## Server(s)
|
||||
|
||||
I was given a Dell R720xd with dual Xeon E5-2580 CPUs (10c/20t),
|
||||
256GB RAM, and 12x4TB (48TB) of raw disk space. It runs my own, my servers, and
|
||||
hosts onsite backups for all my stuff and serves as an offsite encrypted backup
|
||||
for some friends.
|
||||
|
||||
I have a few other cheap machines with larger disks at friends and family's
|
||||
houses for off-site, encrypted backups of important data. They all run NixOS and
|
||||
use [its built-in restic backup setup][backups-nix].
|
||||
|
||||
Any paid client workloads are served via redundant mechanisms via cloud
|
||||
services, generally Digital Ocean, and backed up with whatever the relevant
|
||||
cloud offering is.
|
||||
|
||||
I run the following main applications:
|
||||
|
||||
- [Caddy](https://traefik.io/traefik/) to reverse proxy, TLS-terminate all
|
||||
the things, and serve static, public files
|
||||
- A homemade chat bot for various things
|
||||
- Various game servers (Minecraft, Factorio, Valheim, etc.)
|
||||
- [Gitea](https://about.gitea.com/) for https://git.lyte.dev 💜💛💙
|
||||
- [Jellyfin](https://jellyfin.org/) for streaming my video media to approved
|
||||
users (family, friends, etc.)
|
||||
- [PostgreSQL](https://www.postgresql.org/) as the great database for anything
|
||||
that needs one
|
||||
- [Vaultwarden](https://github.com/dani-garcia/vaultwarden) for sharing and
|
||||
managing passwords
|
||||
- [Atuin](https://atuin.sh) for sync'ing shell histories across my machines
|
||||
- Samba file shares
|
||||
|
||||
Other details can be found in [the Nix config for the `beefcake` host][beefnix].
|
||||
|
||||
I run a few services from the cloud as well:
|
||||
|
||||
- [A small DDNS application](https://github.com/lytedev/deno-netlify-ddns) that
|
||||
machines report to so I have relatively up-to-date public IP information on
|
||||
most of my devices (this can't run from home for fairly obvious reasons 😉)
|
||||
- Each machine runs [the accompanying client](https://github.com/lytedev/deno-netlify-ddns-client) with unique credentials
|
||||
- Various monitoring scripts for specific things (also can't run from home - who
|
||||
would monitor the monitors?)
|
||||
|
||||
# Starting Work
|
||||
|
||||
I sit in a way-too-expensive computer chair at a homemade desk and wiggle my
|
||||
mouse or slap my keyboard until my workstation wakes up. I punch in my password
|
||||
and a script fires off to make sure I am ready to work. It does stuff like have
|
||||
me log in to various work services that need daily (or hourly) authentication
|
||||
and making sure I remember to review certain reminders and things of that
|
||||
nature.
|
||||
|
||||
I usually spend my work mornings reviewing neat things I read about the night
|
||||
before on my various feeds -- assuming nothing urgent is happening with work,
|
||||
which there usually is not. Tinkering with things is super important for
|
||||
learning. This is usually done by pulling down my notes as sync'd from my phone,
|
||||
where I do most of my reading.
|
||||
|
||||
I may also spend some time playing games or working with electronics in the
|
||||
workshop area.
|
||||
|
||||
Then the standup meeting notification pops up and I spend about 10 minutes
|
||||
reviewing work stuff so I'm ready for the day. I make sure any audio/visual
|
||||
settings are reset for the workday from any tinkering I may have done the
|
||||
previous evening, usually with musical instruments or just general goofing
|
||||
around with Pipewire.
|
||||
|
||||
## Chair: Steelcase Gesture with Headrest
|
||||
|
||||
Like a mattress, very subjective. Get your chairs secondhand for way cheap and
|
||||
you can get some heckin' nice chairs. I spend about 8 hours a day in my chair,
|
||||
so having a good chair is well worth it, even if the price tag is $1,500 USD. 😬
|
||||
|
||||
## Desk: Custom
|
||||
|
||||
It's a huge slab of butcher's block I got from Home Depot for about 200USD and
|
||||
I made some _really_ crappy legs to try and hold it up. It's huge and awesome
|
||||
and by the time I can't move it around by myself anymore is probably a good
|
||||
indicator for retirement.
|
||||
|
||||
You can see an old but decent picture of it here:
|
||||
https://files.lyte.dev/images/archives/battle-station-2020.jpg
|
||||
|
||||
## Mouse: Logitech MX Master 3 and Mionix Avior Pro
|
||||
|
||||
The MX Master 3 is my default, go-to mouse. It's great. I don't use any of
|
||||
the fancy features, really, but the mousewheel on it is _real_ special. Can't
|
||||
go back.
|
||||
|
||||
The Avior Pro I got a long time ago after my last mouse bit the dust. I really
|
||||
only pull out for gaming on the more competitive side, such as _Counter Strike_.
|
||||
For pretty much everything else, the MX Master 3 is not noticeably bad -- even
|
||||
for _Doom Eternal_.
|
||||
|
||||
## Keyboard: Sofle Choc RGB
|
||||
|
||||
Another very subjective thing! Building and programming your own keyboard is
|
||||
something that is super fun for heavy keyboard users, which I think is most
|
||||
people these days. This one is focused on being good for my hands and wrists to
|
||||
use for long programming sessions. It's split into two wireless pieces so each
|
||||
hand can move them independently and the keys are laid out in a sensible manner
|
||||
that fits the human hand. There are other features that are nice, but that I
|
||||
rarely use.
|
||||
|
||||
Here's a picture if you like: https://files.lyte.dev/keyboards/zofle.jpg
|
||||
|
||||
## Monitors: Aorus FO48U and 2 Dell U2720Q
|
||||
|
||||
I stumbled into having a huge 4K display when COVID had Postmates send our
|
||||
little Kansas City satellite office packing and I took the meeting hardware
|
||||
home. At some point, I plugged it in to see what _World of Warcraft_ would look
|
||||
like in 4K on a big screen and realized it was actually amazing for programming
|
||||
as well.
|
||||
|
||||
Here's roughly my monitor layout, though I usually have two of the secondary
|
||||
monitors -- one on either side:
|
||||
https://files.lyte.dev/images/archives/battle-station-2022-10-13.3.jpg
|
||||
|
||||
## Laptop: Lenovo T480 and MacBooks
|
||||
|
||||
The T480 is a recent acquisition. It was pretty busted up when I got it, but I bought the
|
||||
high capacity external battery and some phat sticks of RAM and replaced the
|
||||
trackpad. A little bit of superglue and a good cleaning later and I've just
|
||||
fallen in love with this laptop. Sometimes I use it even though I have a really
|
||||
beefy workstation with what I consider to be a pretty high-end setup -- it just
|
||||
feels so cozy! All my machines run Arch Linux configured as specified in my
|
||||
dotfiles repo.
|
||||
|
||||
Work provides MacBooks. I'm able to use them as proxies and simply SSH into them
|
||||
for work-related tasks. They otherwise run Linux virtual machines or serve as
|
||||
dumb terminals to Linux environments in _the cloud_ where I do the majority of
|
||||
my work. In general, I'm not a big of macOS and its interface, but I fully
|
||||
recognize that I'm used to a very bespoke and unique way of interacting with
|
||||
my computer.
|
||||
|
||||
That said, before work really cracked down, the M1 Max MacBook Pro they got
|
||||
me was one of my favorite machines ever. Insane battery life, performance,
|
||||
speakers, display, _**and** the ability to run Linux natively on it_ is going to
|
||||
be really hard to beat. I miss being able to use that thing to its fullest!
|
||||
|
||||
## Desktop: Custom Rig
|
||||
|
||||
Probably easiest to list the parts. I wanted something quiet, small, and cute.
|
||||
In hindsight, I think it would have been more practical to just get a bigger and
|
||||
more expandable system. I'm somehow always out of USB ports.
|
||||
|
||||
- **CPU**: AMD Ryzen 9 5950X
|
||||
- I wanted as many performant cores as possible for as cheaply as possible
|
||||
and reasonable. I did some math and realized that with the time spend
|
||||
running unit tests and compiling code, this thing would pay for itself in a
|
||||
month or two. It did. Love it and looking forward to the next upgrade!
|
||||
- **GPU**: AMD Radeon RX 6700 XT
|
||||
- Bought this at the worst possible time, but it's been a great card. It
|
||||
can't quite keep up with 4K@120Hz for some titles like _Doom Eternal_ on the
|
||||
highest settings, but it's good enough for my current usage.
|
||||
- **RAM**: Some 64GB kit that's more than enough for me
|
||||
- **PSU**: Some small form factor 750W fully modular power supply - it's very
|
||||
cute because it's so little!
|
||||
- **CPU Cooler**: Can't remember what it is exactly, but I bought the biggest,
|
||||
baddest air cooler that would fit with my setup
|
||||
- **Case**: Cooler Master NR200P
|
||||
- **Linux SSD**: Sabrent Rocket 1TB PCIe 4.0 NVMe
|
||||
- I went with PCIe 4.0 since I make pretty heavy use of my disk what with
|
||||
a great internet connection and all - those Docker images ain't gonna sling
|
||||
themselves, y'know!
|
||||
- What is it with "enterprise" Docker images being absolutely gargantuan?!
|
||||
Why must things suck?! Not that I put anything proprietary to work on my
|
||||
personal machine(s) of course!
|
||||
- **Windows SSD**: TeamGroup MP34 2TB PCIe 3.0 NVMe
|
||||
- Occasionally, I boot to Windows for some games that run better or at all,
|
||||
like Valorant or Destiny 2
|
||||
|
||||
## Other Neat Computing Devices
|
||||
|
||||
I also have a PinePhone and a Steam Deck.
|
||||
|
||||
The PinePhone was unfortunately a dud for me personally since MMS is still
|
||||
pretty prevalent in my life in a way that I can't overlook in addition to the
|
||||
notification setup not quite being up to snuff. I am _very_ excited for the
|
||||
time when a Linux (you know what I mean) phone is feasible, though! I got the
|
||||
keyboard addon, though, and since my phone has some radio issues in the US, I
|
||||
sometimes use it when I need better radio performance, like when camping.
|
||||
|
||||
The Steam Deck is absolutely wonderful. Anything I would play with a controller,
|
||||
I usually just play it on the Steam deck. I haven't touched my Switch since!
|
||||
Plus, it's Linux, so the tinker factor is there too. Highly recommend one if
|
||||
you're considering it.
|
||||
|
||||
## Headphones: Sony WH-1000XM4
|
||||
|
||||
Bought the XM3s on a Black Friday special and fell in love. _Nice_ noise
|
||||
cancelling headphones are absolutely wonderful. They made my mom cry.
|
||||
|
||||
Work got almost all their engineers the XM4s not long after so I kind of have
|
||||
2 pair. The XM4s are _much_ nicer with multiple device pairing and the firmware
|
||||
voice feedback fading out your audio instead of cutting it completely when it
|
||||
says anything. The XM4s also automatically shut down if they can detect they
|
||||
aren't on your head for a little bit, which makes the main issue I had with the
|
||||
XM3s of setting them on my desk without turning them off a non-issue.
|
||||
|
||||
## Microphone: Blue Yeti USB
|
||||
|
||||
Sounds good enough, but I've got a fancy audio interface now, so I'm wanting to
|
||||
upgrade at some point. Super low priority, though.
|
||||
|
||||
## Audio Interface: MOTU M4
|
||||
|
||||
A recent gift. I'm hoping to do more with music and mixing in the future,
|
||||
though, so it will be welcome at that point! I play drums and would love to
|
||||
put together a decent electric guitar setup. I also have an Arturia MicroFreak
|
||||
digital synthesizer which is a lot of fun to play with. I'd love to put some
|
||||
cool tracks together!
|
||||
|
||||
## Keys: Some twist-lock cable keyring
|
||||
|
||||
It's nicer than those metal rings you have to snap your thumbnail to get keys
|
||||
off of. I had one fail after about 10 years and my keys went everywhere, so if
|
||||
you're gonna use these, I guess you should replace them occasionally or check
|
||||
them in rather rough ways with some regularity after a certain amount of time.
|
||||
|
||||
These are usually karabiner'd to my backpack or a belt loop.
|
||||
|
||||
When I'm driving with the family, we take the van, which has one of those
|
||||
wireless keys, so I can just throw my whole bag in and we can drive. If I'm
|
||||
driving solo, it's still a good, old insert-and-turn key, so the keys un-
|
||||
karabiner from the bag or loop and go back on when disembarking.
|
||||
|
||||
My keyring is also attached to my wallet. Speaking of which...
|
||||
|
||||
## Wallet: Chums Surfshorts Wallet
|
||||
|
||||
Cheap, has a zipper to hold keyfobs and cards, and easily attaches to my
|
||||
keys. Having them all together means I just gotta grab two things, one for each
|
||||
pocket: phone and wallet-keys.
|
||||
|
||||
I haven't tried tap-to-paying through the wallet, which is probably a big
|
||||
security hazard. 🤷
|
||||
|
||||
# Software
|
||||
|
||||
When my machine boots up, I'm greeted by the standard Linux login at the
|
||||
console. No display manager or graphical login or anything. Once I'm logged in,
|
||||
I usually run `wm` which fires up my window manager, Sway.
|
||||
|
||||
When Sway starts, it runs Kitty, my terminal emulator of choice, and Firefox, my
|
||||
web browser of choice.
|
||||
|
||||
Anywhere I can, I really like to use the
|
||||
[Catppuccin color scheme](https://github.com/catppuccin/catppuccin).
|
||||
Otherewise, I used a modified Monokai with a darkened
|
||||
background color for the longest time.
|
||||
|
||||
## Web Applications
|
||||
|
||||
Firefox is awesome. I'm a big fan. I make heavy use of their "Sync" offering,
|
||||
which syncs just about everything. It's very convenient and I'm sure it will
|
||||
somehow bite me later.
|
||||
|
||||
I use the following must-have browser extensions:
|
||||
|
||||
- [hide-scrollbars](https://addons.mozilla.org/en-US/firefox/addon/hide-scrollbars/) to hide scroll bars
|
||||
- [FoxyProxy](https://addons.mozilla.org/en-US/firefox/addon/foxyproxy-standard/) for proxying through to various resources
|
||||
- [PassFF](https://github.com/passff/passff) for interfacing with my `password-store`
|
||||
- [Firefox Multi-Account Containers](https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/) for being logged into all my accounts simulataneously
|
||||
- This is probably good enough reason _alone_ to use Firefox over anything else
|
||||
- [Tree Style Tab](https://addons.mozilla.org/en-US/firefox/addon/tree-style-tab/) for a much nicer way of managing four billion tabs
|
||||
- [Dark Reader](https://darkreader.org/) to keep things easy on my eyes
|
||||
- [uBlock Origin](https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/) for blocking ads
|
||||
- [Vimium](https://addons.mozilla.org/en-US/firefox/addon/vimium-ff/) for moar keyboard shortcuts
|
||||
- [open-url-in-container](https://github.com/honsiorovskyi/open-url-in-container) for programmatically opening URLs in certain Multi-Account Containers, for opening certain tabs in certain containers from my terminal
|
||||
|
||||
Firefox usually has the following web applications opened:
|
||||
|
||||
- [Shortwave](https://shortwave.com) as my email client for my Google Mail
|
||||
accounts to which my other emails forward to
|
||||
- [Linear](https://linear.app) for personal task management (a better Jira/
|
||||
Trello IMHO)
|
||||
- Google Calendar for scheduling, planning, event management, etc. for both work
|
||||
and personal life
|
||||
- [Hacker News](https://news.ycombinator.com) for mostly-relevant and
|
||||
interesting links to me and for high-quality discussions
|
||||
- [Lobsters](https://lobste.rs) for reasons similar to Hacker News
|
||||
- [Lemmy.world](https://lemmy.world) for federated link aggregation and
|
||||
discussions since Reddit
|
||||
killed API access for my clients of choice
|
||||
- [GitHub](https://github.com) for open-source and similar work
|
||||
- [Band](https://band.us) for communicating with friends, family, and church
|
||||
folks
|
||||
- [Discord](https://discord.com) for communicating with friends, family, "more-
|
||||
hip" church folks, various communities, and other acquaintences
|
||||
- [Element](https://element.io) for communicating with friends, coworkers,
|
||||
various communities, and other acquaintencas
|
||||
- [Slack](https://slack.com) for communicating with friends, coworkers, and
|
||||
various communities
|
||||
- [Spotify](https://spotify.com) for music
|
||||
- Various applications specific to work, such as Okta, Jira, GitLab, etc.
|
||||
|
||||
Ugh, modern messaging is a mess, isn't it?
|
||||
|
||||
[Tailscale](https://tailscale.com) connects all my machines to the same
|
||||
VPN. It's great! And I think once I get it fully setup, I will put it in the
|
||||
"gamechanger" bucket.
|
||||
|
||||
I also frequent these:
|
||||
|
||||
- https://git.lyte.dev for personal code management
|
||||
- https://a.lyte.dev for personal online analytics
|
||||
- https://bw.lyte.dev for shared password database management
|
||||
|
||||
And do my online shopping here:
|
||||
|
||||
- ebay.com
|
||||
- facebook.com/marketplace
|
||||
- craigslist.com
|
||||
- amazon.com
|
||||
- aliexpress.us
|
||||
|
||||
I'm sure I'm forgetting a ton here.
|
||||
|
||||
## Terminal
|
||||
|
||||
Beyond the web stuff, I pretty much live in the terminal. Interacting with my
|
||||
machine is mostly done via hotkeys as configured for Sway. Otherwise, everything
|
||||
happens in the terminal. Here are my most popular commands in no particular
|
||||
order:
|
||||
|
||||
- [`fish`](https://github.com/fish-shell/fish-shell) as my interactive shell
|
||||
(and sometimes for scripts, too!)
|
||||
- [`helix`](https://github.com/helix-editor/helix) for text editing
|
||||
- [`git`](https://git-scm.com) for code version management (source control)
|
||||
- I use [`git-delta`](https://github.com/dandavison/delta) for viewing diffs
|
||||
- [`pass`](https://www.passwordstore.org) for passwords and secrets management
|
||||
- [`ssh`](https://www.openssh.com) and [`mosh`](https://mosh.org) for
|
||||
accessing other machines
|
||||
- [`tmux`](https://github.com/tmux/tmux) and [`zellij`](https://zellij.dev) for
|
||||
multiplexing terminals
|
||||
- [`htop`](https://htop.dev) and
|
||||
[`btm` (or `bottom`)](https://github.com/ClementTsang/bottom) for process
|
||||
management and resource monitoring
|
||||
- [`rtx`](https://github.com/jdxcode/rtx) for managing various runtimes' and
|
||||
applications' versions
|
||||
- [`sk` (or `skim`)](https://github.com/lotabout/skim) for fuzzy searching for
|
||||
stuff
|
||||
- [`rg` (or `ripgrep`)](https://github.com/BurntSushi/ripgrep) for specific
|
||||
searching for stuff
|
||||
- [`sd`](https://github.com/chmln/sd) for most things I used to use `sed` for
|
||||
- [`nnn`](https://github.com/jarun/nnn) and
|
||||
[`broot`](https://github.com/Canop/broot) for filesystem browsing and
|
||||
navigation
|
||||
- [`bat`](https://github.com/sharkdp/bat) for viewing files as a `cat`
|
||||
replacement
|
||||
- [`exa`](https://github.com/ogham/exa) as an `ls` replacement
|
||||
- [`man`](https://wiki.archlinux.org/title/man_page) for reading documentation
|
||||
- [`xh`](https://github.com/ducaale/xh) and [`curl`](https://curl.se) for
|
||||
interacting with HTTP endpoints (I want to check out
|
||||
[`hurl`](https://github.com/Orange-OpenSource/hurl), too!)
|
||||
- [`jq`](https://github.com/jqlang/jq),
|
||||
[`gron`](https://github.com/tomnomnom/gron), and
|
||||
[`jql`](https://github.com/cube2222/jql) for interacting with JSON data
|
||||
- [`rsync`](https://rsync.samba.org) for moving files amongst machines
|
||||
- [`restic`](https://restic.net) for local and remote deduplicated, encrypted,
|
||||
and automated backups
|
||||
- [`watchexec`](https://github.com/watchexec/watchexec) for doing stuff as I
|
||||
edit files (like running unit tests anytime code is changed)
|
||||
- [`age`](https://github.com/FiloSottile/age) and
|
||||
[`sops`](https://github.com/mozilla/sops) for secrets management
|
||||
- [`dua`](https://github.com/Byron/dua-cli/) for disk usage analysis
|
||||
- [`sc-im`](https://github.com/andmarti1424/sc-im) for managing two-dimensional,
|
||||
relational data (spreadsheets)
|
||||
- [`pulsemixer`](https://github.com/GeorgeFilipkin/pulsemixer) for adjusting
|
||||
audio levels and volumes
|
||||
- [`bluetoothctl`](http://www.bluez.org) for managing bluetooth devices (also
|
||||
[`bluetuith`](https://github.com/darkhz/bluetuith) for a TUI!)
|
||||
- [`weechat`](https://weechat.org/) as my IRC client
|
||||
- This is usually running in a persistent `tmux` or `zellij` session on a
|
||||
server that I remote into
|
||||
- I occasionally use the relay functionality that `weechat` offers as well
|
||||
- [`docker`](https://docker.com) and [`podman`](https://podman.io) for container
|
||||
management
|
||||
- [`hexyl`](https://github.com/sharkdp/hexyl) when I need to look at binary data
|
||||
- [`make`](https://www.gnu.org/software/make/) for doing things describe in
|
||||
`Makefile`s
|
||||
|
||||
## GUI
|
||||
|
||||
From Sway, the only utilities I use are the following:
|
||||
|
||||
- [`waybar`](https://github.com/Alexays/Waybar) shows the time, a HUD for my
|
||||
virtual desktops, various volume information, and a high level overview of
|
||||
system resource usage.
|
||||
- [`mako`](https://github.com/emersion/mako) shows me notifications and let's me
|
||||
interact with them.
|
||||
- [`gammastep`](https://gitlab.com/chinstrap/gammastep) makes my displays
|
||||
orange-y at night time.
|
||||
|
||||
Beyond these, I have a bunch of scripts and configuration in my
|
||||
[dotfiles repo](https://git.lyte.dev/lytedev/dotfiles).
|
||||
|
||||
I use [`wofi`](https://hg.sr.ht/~scoopta/wofi) for launching applications
|
||||
occasionally. It lists the following often-used applications:
|
||||
|
||||
- [Steam](https://steampowered.com) for installing and running games
|
||||
- [Slippi](https://slippi.gg) for playing Super Smash Brother Melee online!
|
||||
- [Lutris](https://lutris.net) for running World of Warcraft
|
||||
- [`qpwgraph`](https://gitlab.freedesktop.org/rncbc/qpwgraph) for routing audio
|
||||
via [`wireplumber`](https://pipewire.pages.freedesktop.org/wireplumber/)
|
||||
- [Inkscape](https://inkscape.org) for editing vector graphics (like SVGs) and
|
||||
image files
|
||||
- [Audacity](https://www.audacityteam.org) for recording audio
|
||||
- [Krita](https://krita.org) and [GIMP](https://www.gimp.org) for editing
|
||||
non-vector (bitmap?) graphics and image files
|
||||
- [KDE Connect](https://kdeconnect.kde.org) for when I want phone notifications
|
||||
to be mirrored to my desktop
|
||||
- Usually only when expecting a specific call or message
|
||||
- [Visual Studio Code](https://code.visualstudio.com) for pairing with other VS
|
||||
Code folks or troubleshooting a VS Code user's setup
|
||||
- [Thunar](https://docs.xfce.org/xfce/thunar/start) for managing files in a GUI
|
||||
- [PulseAudio Volume Control (or `pavucontrol`)](https://freedesktop.org/software/pulseaudio/pavucontrol/)
|
||||
|
||||
## Music
|
||||
|
||||
I dabble in music sometimes, depending on my workload or how much I need an
|
||||
outlet, mostly as a poser metal drummer, so I'll list that stuff here, too!
|
||||
|
||||
- Roland TD-11 electric drum kit
|
||||
- Electric is convenient for adding music and not bothering people in a
|
||||
quarter-mile radius
|
||||
- [Here's a video of me playing it](https://www.youtube.com/watch?v=ucpYCSxxvy0)
|
||||
- Arturia MicroFreak digital synthesizer
|
||||
- Very new to me; super fun to play with
|
||||
- Novation LaunchKey 61 MIDI controller
|
||||
- Haven't taken the time yet to really setup a DAW to work with this
|
||||
- Some piece of crap first act electric guitar that I keep trying to fix and make work
|
||||
- I just need to bite the bullet and save up for a nice Ibanez (used of course)
|
||||
|
||||
## Electronics
|
||||
|
||||
I also tinker a lot with electronics and "maker"-y things! The only name-able
|
||||
thing is my 3D printer, which is an Ender CR-10S. Micro Center was having an
|
||||
incredible sale one upon a day. The thing is really awesome. I still want a real
|
||||
nice one that I can reliably send prints over the network to.
|
||||
|
||||
Other unnamed tools I have and use on roughly a weekly basis:
|
||||
|
||||
- Soldering iron
|
||||
- Solder sucker
|
||||
- Air purifier (solder _and_ 3D printer fumes ain't all that good for you)
|
||||
|
||||
And I'm sure there are other unsung heroes I just don't think about. Like butter
|
||||
knives.
|
||||
|
||||
# Finishing The Day
|
||||
|
||||
That about does it! I usually head upstairs when work is done, make food in
|
||||
unidentifiable cookware, tinker and play with my kids for a bit, head out to
|
||||
whatever evening activity we've got going on if applicable, come home, and bedtime!
|
||||
|
||||
Ah, we have a couple tablets for Khan Academy Kids, white noise (and other
|
||||
sleep-inducing ambience), and podcasts (like Base Camp Adventures!) and a
|
||||
Google Home Mini or two, mostly for playing loud and obnoxious music or setting
|
||||
timers.
|
||||
|
||||
[uses-history]: https://git.lyte.dev/lytedev/site.lyte.dev/commits/branch/master/content/uses.md
|
||||
[backups-nix]: https://git.lyte.dev/lytedev/nix/src/commit/fafd242e461620aaa48a669b3623614cc6829700/nixos/beefcake.nix#L528-L573
|
||||
[beefnix]: https://git.lyte.dev/lytedev/nix/src/branch/main/nixos/beefcake.nix
|
61
flake.lock
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733581040,
|
||||
"narHash": "sha256-Qn3nPMSopRQJgmvHzVqPcE3I03zJyl8cSbgnnltfFDY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "22c3f2cf41a0e70184334a958e6b124fb0ce3e01",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
30
flake.nix
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
...
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
hugo
|
||||
gnumake
|
||||
netlify-cli
|
||||
nodejs
|
||||
];
|
||||
shellHook = ''
|
||||
npm install
|
||||
PATH="$(realpath node_modules/.bin):''${PATH}"
|
||||
'';
|
||||
};
|
||||
});
|
||||
}
|
|
@ -2,20 +2,28 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="preload" href="/font/iosevkalytewebmin/iosevkalyteweb-regular.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/styles.css" as="style">
|
||||
<link rel="modulepreload" href="/theme.mjs">
|
||||
<link rel="modulepreload" href="/global.mjs">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{ if .Params.summary }}{{ .Params.summary }}{{ else }}{{ .Site.Params.Description }}{{ end }}">
|
||||
<title>{{ block "title" . }}{{ .Site.Title }}{{ end }}</title>
|
||||
<link rel="shortcut icon" href="/icon.png" />
|
||||
<link defer rel="stylesheet" href="/styles.css" />
|
||||
<script defer src="/global.js"></script>
|
||||
<script async defer data-domain="lyte.dev" src="https://a.lyte.dev/js/plausible.js"></script>
|
||||
<script defer src="//instant.page/5.1.0" type="module" integrity="sha384-by67kQnR+pyfy8yWP4kPO12fHKRLHZPfEsiSXR8u2IKcTdxD805MGUXBzVPnkLHw"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script defer type="module" src="/global.mjs"></script>
|
||||
<script async defer data-domain="lyte.dev" src="https://a.lyte.dev/js/script.js"></script>
|
||||
</head>
|
||||
<body class="system-theme nojs">
|
||||
<noscript>
|
||||
<style type="text/css">
|
||||
html > body.nojs > main, html > body.nojs > footer { visibility: inherit; }
|
||||
</style>
|
||||
</noscript>
|
||||
<header>
|
||||
<a href="#start-of-content" class="hide-unless-focused">Skip to Content</a>
|
||||
<section>
|
||||
<a href="/" aria-label="index link" id="logo" title="go to site index">
|
||||
<a href="/" aria-label="index link" id="logo">
|
||||
{{ partial "logo.svg.html" . }}
|
||||
</a>
|
||||
<a href="/">
|
||||
|
@ -25,23 +33,94 @@
|
|||
<section>
|
||||
{{ $currentPage := . }}
|
||||
{{ range .Site.Menus.main }}
|
||||
{{ $active := or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) }}
|
||||
{{ $active = or $active (eq .Name $currentPage.Title) }}
|
||||
{{ $active = or $active (and (eq .Name "Blog") (eq $currentPage.Section "post")) }}
|
||||
{{ $active = or $active (and (eq .Name "Tags") (eq $currentPage.Section "tags")) }}
|
||||
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||
{{ $active := or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) }}
|
||||
{{ $active = or $active (eq .Name $currentPage.Title) }}
|
||||
{{ $active = or $active (and (eq .Name "Blog") (eq $currentPage.Section "post")) }}
|
||||
{{ $active = or $active (and (eq .Name "Tags") (eq $currentPage.Section "tags")) }}
|
||||
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||
{{ end }}
|
||||
</section>
|
||||
<section style="position:absolute;height:100%;right:0" aria-hidden="true">
|
||||
<button class="no-bg theme-toggler js-only centerize" aria-label="toggle dark theme" title="toggle dark theme">
|
||||
<section style="position:absolute;height:100%;right:0">
|
||||
<button id="theme-toggler" class="no-bg theme-toggler js-only centerize" title="toggle theme" aria-label="toggle theme">
|
||||
{{ partial "theme-toggle.html" . }}
|
||||
</button>
|
||||
<button class="no-bg align-toggler js-only hidden-on-mobile centerize" aria-label="toggle text alignment" title="toggle text alignment">
|
||||
<button id="align-toggler" class="no-bg align-toggler js-only hidden-on-mobile centerize" title="toggle alignment" aria-label="toggle alignment">
|
||||
{{ partial "align-toggle.html" . }}
|
||||
</button>
|
||||
</section>
|
||||
</header>
|
||||
<main id="start-of-content">{{ block "main" . }}{{ .Content }}{{ end }}</main>
|
||||
<footer style="padding-bottom: 5em"></footer>
|
||||
<footer>
|
||||
<h2 id="footer-links">External Links</h2>
|
||||
|
||||
<ul class="horizontal-blocks">
|
||||
<li>
|
||||
<a href="//files.lyte.dev">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
|
||||
</svg>
|
||||
Files
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://discord.gg/jUCXCYp">
|
||||
<svg fill="currentColor" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"/></svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:daniel@lyte.dev">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
|
||||
</svg>
|
||||
Email
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.lyte.dev/lytedev">
|
||||
<svg height="24" width="24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M439.55 236.05L244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
|
||||
git.lyte.dev
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/lytedev">
|
||||
<svg height="24" width="24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="footer-meta">Meta</h2>
|
||||
|
||||
<ul class="horizontal-blocks">
|
||||
<li>
|
||||
<a href="/privacy">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
|
||||
</svg>
|
||||
Privacy Policy
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/salary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" width="24" height="24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Salary Transparency
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.lyte.dev/lytedev/site.lyte.dev">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />
|
||||
</svg>
|
||||
Site Code
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p style="text-align: center;">Thanks for visiting and reading! You probably want to go <a href="javascript:history.go(-1)">back</a> or to <a href="/">the index</a> now.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
{{ define "main" }}
|
||||
{{ .Content }}
|
||||
<ul>
|
||||
{{ range .Pages }}
|
||||
{{ .Render "li" }}
|
||||
{{ else }}
|
||||
<li>Looks like there's nothing here!... yet!</li>
|
||||
{{ end }}
|
||||
|
||||
<ul style="padding: 0;">
|
||||
{{ range .Pages }}
|
||||
{{ .Render "li" }}
|
||||
{{ else }}
|
||||
<p>Looks like there's nothing here!... yet!</p>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{ define "title" }}
|
||||
|
|
|
@ -3,11 +3,19 @@
|
|||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<h2>{{ .Title }}</h2>
|
||||
<h2 id="{{ .Title }}"><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
|
||||
{{ with .Lastmod }}
|
||||
<p>
|
||||
Posted on {{ dateFormat "Jan 2 2006" . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
{{ if not (isset .Params "toc") }}
|
||||
<details class="left-border" style="margin-bottom: 2.5em;">
|
||||
<summary>
|
||||
<h3>Table of Contents</h3>
|
||||
</summary>
|
||||
{{ .TableOfContents }}
|
||||
</details>
|
||||
{{ end }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<li style="margin-bottom: 0.5em">
|
||||
{{ dateFormat "Jan 2 2006" .Lastmod }}
|
||||
<br />
|
||||
<a href="{{ .Permalink }}">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
</li>
|
||||
<div class="left-border">
|
||||
<h3>
|
||||
<a style="flex-shrink: 1; margin-right: auto;" href="{{ .Permalink }}">{{ .Title }}</a>
|
||||
{{ if .Draft }}<span class="badge">DRAFT</span>{{end}}
|
||||
<br />
|
||||
<small>Posted on {{ dateFormat "Jan 2 2006" (cond .Date.IsZero .Lastmod .Date) }}</small>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
{{ .Summary }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -2,42 +2,18 @@
|
|||
|
||||
{{ .Content }}
|
||||
|
||||
<h2>Latest Posts (<a target="_blank" href="/blog/index.xml">RSS</a>)</h2>
|
||||
<details open>
|
||||
<summary>
|
||||
<h2 id="latest-posts">
|
||||
Latest <a href="/blog">Blog</a> Posts (<a target="_blank" href="/blog/index.xml">RSS</a>)
|
||||
</h2>
|
||||
</summary>
|
||||
|
||||
<ul>
|
||||
{{ range (where .Site.RegularPages "Section" "blog") }}
|
||||
{{ .Render "li" }}
|
||||
{{ else }}
|
||||
<p>Looks like there's nothing here!... yet!</p>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
<h2>External Links</h2>
|
||||
|
||||
<ul class="horizontal-blocks">
|
||||
<li>
|
||||
<a href="https://discord.gg/jUCXCYp">Discord</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:daniel@lyte.dev">Email</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/lytedev">GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.lyte.dev/lytedev">git.lyte.dev</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Meta</h2>
|
||||
|
||||
<ul class="horizontal-blocks">
|
||||
<li>
|
||||
<a href="/privacy">Privacy Policy</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.lyte.dev/lytedev/site.lyte.dev">Site Code</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
{{ end }}
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
<svg class="hide-in-align-left" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><rect fill="none" height="24" width="24"/><path style="fill:var(--fg)" d="M4,22H2V2h2V22z M22,7H6v3h16V7z M16,14H6v3h10V14z"/></svg>
|
||||
<svg class="hide-in-align-center" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path style="fill:var(--fg)" d="M7 15v2h10v-2H7zm-4 6h18v-2H3v2zm0-8h18v-2H3v2zm4-6v2h10V7H7zM3 3v2h18V3H3z"/></svg>
|
||||
<svg class="hide-in-align-center" width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12H12m-8.25 5.25h16.5" />
|
||||
</svg>
|
||||
<svg class="hide-in-align-left" width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M7.75 12 h8.5 m-12.25 5.25 h16.5" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 543 B After Width: | Height: | Size: 559 B |
|
@ -1,2 +1,9 @@
|
|||
<svg class="hide-in-light-theme" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/><path style="fill:var(--fg)" d="M20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69zm-2 5.79V18h-3.52L12 20.48 9.52 18H6v-3.52L3.52 12 6 9.52V6h3.52L12 3.52 14.48 6H18v3.52L20.48 12 18 14.48zM12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"/></svg>
|
||||
<svg class="hide-in-dark-theme" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/><path style="stroke:var(--fg)" d="M10 4c4.41 0 8 3.59 8 8s-3.59 8-8 8c-.34 0-.68-.02-1.01-.07C10.9 17.77 12 14.95 12 12s-1.1-5.77-3.01-7.93C9.32 4.02 9.66 4 10 4m0-2c-1.82 0-3.53.5-5 1.35C7.99 5.08 10 8.3 10 12s-2.01 6.92-5 8.65C6.47 21.5 8.18 22 10 22c5.52 0 10-4.48 10-10S15.52 2 10 2z"/></svg>
|
||||
<svg class="only-in-light-theme" height="24" width="24" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
||||
</svg>
|
||||
<svg class="only-in-dark-theme" height="24" width="24" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
|
||||
</svg>
|
||||
<svg class="only-in-system-theme" height="24" width="24" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 966 B After Width: | Height: | Size: 1.4 KiB |
8
layouts/tips/li.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<h3>
|
||||
<a style="flex-shrink: 1; margin-right: auto;" href="{{ .Permalink }}">
|
||||
{{ .Title }}
|
||||
</a>
|
||||
<br />
|
||||
<small>Posted on {{ dateFormat "Jan 2 2006" (cond .Date.IsZero .Lastmod .Date) }}</small>
|
||||
</h3>
|
||||
{{ .Content }}
|
13
layouts/tips/single.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{{ define "title" }}
|
||||
{{ .Title }} - {{ .Site.Title }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "main" }}
|
||||
<h2 id="{{ .Title }}"><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
|
||||
{{ with .Lastmod }}
|
||||
<p>
|
||||
Posted on {{ dateFormat "Jan 2 2006" . }}
|
||||
</p>
|
||||
{{ end }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
10
makefile
|
@ -7,11 +7,16 @@ LOCAL_IP ?= $(shell ip a | grep -oP '192\.168\.\d+\.\d+/' | head -n 1 | tr -d '/
|
|||
all: build
|
||||
|
||||
.PHONY: build
|
||||
build: static/font.css static/styles.css ; @${HUGO}
|
||||
build: static/styles.css ; @${HUGO}
|
||||
|
||||
.PHONY: public
|
||||
public: build
|
||||
|
||||
.PHONY: serve
|
||||
serve:
|
||||
@stylus -w src/stylus/styles.styl --sourcemap -o static/styles.css &
|
||||
@${HUGO} serve
|
||||
|
||||
.PHONY: dev
|
||||
dev:
|
||||
@stylus -w src/stylus/styles.styl --sourcemap -o static/styles.css &
|
||||
|
@ -31,9 +36,6 @@ publish: clean-css public ; @netlify ${NETLIFY_DEPLOY} && echo "Run \`make publi
|
|||
.PHONY: publish-prod
|
||||
publish-prod: clean-css public ; @netlify ${NETLIFY_DEPLOY} --prod
|
||||
|
||||
static/font.css: src/stylus/font.styl
|
||||
stylus --compress $< -o $@
|
||||
|
||||
static/styles.css: src/stylus/styles.styl $(shell find src/stylus -regex ".*\.styl")
|
||||
stylus --compress $< -o $@
|
||||
|
||||
|
|
12
netlify.toml
|
@ -1,7 +1,17 @@
|
|||
[[headers]]
|
||||
for = "/font/iosevka/*"
|
||||
[headers.values]
|
||||
Access-Control-Allow-Origin = "https://git.lyte.dev"
|
||||
Access-Control-Allow-Origin = "*"
|
||||
|
||||
[[headers]]
|
||||
for = "/font/iosevkalytewebmin/*"
|
||||
[headers.values]
|
||||
Access-Control-Allow-Origin = "*"
|
||||
|
||||
[[headers]]
|
||||
for = "/styles.css"
|
||||
[headers.values]
|
||||
Access-Control-Allow-Origin = "*"
|
||||
|
||||
[[headers]]
|
||||
for = "/.well-known/matrix/*"
|
||||
|
|
165
package-lock.json
generated
Normal file
|
@ -0,0 +1,165 @@
|
|||
{
|
||||
"name": "site.lyte.dev",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "site.lyte.dev",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"stylus": "^0.60.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz",
|
||||
"integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA=="
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/stylus": {
|
||||
"version": "0.60.0",
|
||||
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.60.0.tgz",
|
||||
"integrity": "sha512-j2pBgEwzCu05yCuY4cmyp0FtPQQFBBAGB7TY7QaNl7eztiHwkxzwvIp5vjZJND/a1JNOka+ZW9ewVPFZpI3pcA==",
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "~4.2.0",
|
||||
"debug": "^4.3.2",
|
||||
"glob": "^7.1.6",
|
||||
"sax": "~1.2.4",
|
||||
"source-map": "^0.7.3"
|
||||
},
|
||||
"bin": {
|
||||
"stylus": "bin/stylus"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/stylus"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
8
package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "site.lyte.dev",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"stylus": "^0.60.0"
|
||||
}
|
||||
}
|
|
@ -1,4 +1,11 @@
|
|||
# [lyte.dev][/]
|
||||
<div align="center">
|
||||
|
||||
<h1>
|
||||
<img width="100" src="static/img/logo.svg" /> <br>
|
||||
<a href="https://lyte.dev">lyte.dev</a>
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
|
|
@ -1,67 +1,157 @@
|
|||
code, pre
|
||||
font-family var(--msff)
|
||||
|
||||
ul, ol { padding-left: 2em }
|
||||
|
||||
// hide main content until js runs
|
||||
// a noscript tag contains a stylesheet that counters this
|
||||
body.nojs > main, body.nojs > footer
|
||||
visibility hidden
|
||||
|
||||
a
|
||||
cursor pointer
|
||||
text-decoration-skip-ink auto
|
||||
color var(--link-fg)
|
||||
&:visited { color: var(--link-visited-fg) }
|
||||
|
||||
img, embed, frame, iframe { max-width: 100vw }
|
||||
footer a > svg,
|
||||
main a > svg
|
||||
margin-right: 0.5em
|
||||
|
||||
body
|
||||
button.copy-code-button
|
||||
color var(--link-fg)
|
||||
text-decoration underline
|
||||
z-index 10
|
||||
position relative
|
||||
align-self flex-start
|
||||
position absolute
|
||||
right 0
|
||||
opacity 0.75
|
||||
|
||||
&:hover
|
||||
opacity 1.0
|
||||
|
||||
@media (max-width: 600px)
|
||||
pre.chroma:hover > button.copy-code-button
|
||||
opacity 0.9
|
||||
|
||||
img, embed, frame, iframe { max-width: 100% }
|
||||
|
||||
details
|
||||
position relative
|
||||
|
||||
details > summary
|
||||
cursor pointer
|
||||
list-style none
|
||||
text-decoration underline
|
||||
color var(--link-fg)
|
||||
|
||||
details > summary::-webkit-details-marker
|
||||
display none
|
||||
|
||||
details > summary::after
|
||||
position absolute
|
||||
padding 0 1em
|
||||
top 0
|
||||
right -1em
|
||||
// background-image url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' style='width: 1em; height: 1em;'><path stroke-linecap='round' stroke-linejoin='round' d='M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z' /></svg>")
|
||||
content "+"
|
||||
|
||||
details[open] > summary::after
|
||||
content "-"
|
||||
|
||||
.left-border
|
||||
position relative
|
||||
padding-left 0.5em
|
||||
border-left solid var(--syntax-bpx) var(--syntax-ledg)
|
||||
transition 0.2s border-color
|
||||
|
||||
&:hover
|
||||
border-color var(--syntax-ledgh)
|
||||
|
||||
html,body
|
||||
min-height 100vh
|
||||
background-color var(--bg)
|
||||
color var(--fg)
|
||||
font-family var(--ff)
|
||||
font-weight 400
|
||||
font-size 1rem
|
||||
|
||||
&> footer
|
||||
padding 0.5em
|
||||
padding-top 5em
|
||||
padding-bottom 5em
|
||||
|
||||
&> main
|
||||
padding 0.5em
|
||||
line-height 1.6em
|
||||
|
||||
.highlight
|
||||
max-width 100vw
|
||||
border-left solid var(--syntax-bpx) var(--syntax-ledg)
|
||||
main:first-child, footer:first-child
|
||||
margin-top 0
|
||||
|
||||
.highlight, h1, h2, h3, h4, h5, h6, form, ul, ol, p
|
||||
> main, > footer
|
||||
.highlight
|
||||
pre.chroma
|
||||
position relative
|
||||
background-color var(--syntax-bg)
|
||||
max-width 100vw
|
||||
border-left solid var(--syntax-bpx) var(--syntax-ledg)
|
||||
display flex
|
||||
transition 0.2s border-color
|
||||
|
||||
&:hover
|
||||
border-color var(--syntax-ledgh)
|
||||
|
||||
li
|
||||
margin-top 0.5em
|
||||
margin-bottom 0.5em
|
||||
|
||||
.highlight, form, > ul, > ol, p
|
||||
margin-top 1em
|
||||
margin-bottom 0.25em
|
||||
|
||||
h1, h2, h3, h4, h5, h6, details
|
||||
margin-top 2em
|
||||
margin-bottom 0.25em
|
||||
|
||||
h1, h2, h3, h4, h5, h6, form, ul, ol, p
|
||||
max-width 60ch
|
||||
|
||||
> h1 + p, > h2 + p, > h3 + p, > h4 + p, > h5 + p, > h6 + p { margin-top: 0 }
|
||||
> h1, > h2, > h3, > h4, > h5, > h6 { color: var(--heading-fg) }
|
||||
*:first-child { margin-top: 0 }
|
||||
|
||||
> h1, > h2, > h3, > h4, > h5, > h6
|
||||
display flex
|
||||
h1, h2, h3, h4, h5, h6
|
||||
position relative
|
||||
display block
|
||||
|
||||
.anchor-link
|
||||
flex-grow 1
|
||||
padding 0 0.5em
|
||||
font-weight normal
|
||||
font-size 75%
|
||||
text-decoration none
|
||||
display none
|
||||
text-decoration underline
|
||||
color transparent
|
||||
|
||||
&:focus-visible
|
||||
color var(--fg)
|
||||
|
||||
&:hover
|
||||
.anchor-link
|
||||
display flex
|
||||
color var(--fg)
|
||||
|
||||
@media (max-width: 600px)
|
||||
> h1, > h2, > h3, > h4, > h5, > h6
|
||||
h1, h2, h3, h4, h5, h6
|
||||
.anchor-link
|
||||
padding 0 0.25em
|
||||
font-weight normal
|
||||
font-size 75%
|
||||
text-decoration none
|
||||
display flex
|
||||
position absolute
|
||||
right 0
|
||||
top 0
|
||||
|
||||
> p code
|
||||
padding 0.1em 0.25em
|
||||
> p code, > ol code, > ul code, > h1 code, > h2 code, > h3 code, > h4 code, > h5 code, > h6 code, blockquote code
|
||||
padding 0.25em
|
||||
border-radius 0.25em
|
||||
background-color var(--inline-code-bg)
|
||||
|
||||
|
@ -120,6 +210,21 @@ button, input[type=submit]
|
|||
.lead { font-size: 1.5rem }
|
||||
.rounded { border-radius: 0.5em }
|
||||
|
||||
table
|
||||
border-collapse collapse
|
||||
|
||||
table tr > td.leading
|
||||
padding-right 1em
|
||||
|
||||
table tr > th
|
||||
table tr > td
|
||||
vertical-align center
|
||||
padding 0.25em 0.5em
|
||||
border solid 1px var(--input-focus-bg)
|
||||
|
||||
table tr > th
|
||||
text-align center
|
||||
|
||||
form
|
||||
&> label, > fieldset
|
||||
border 0
|
||||
|
@ -142,7 +247,7 @@ form
|
|||
align-items center
|
||||
align-content center
|
||||
|
||||
#icon-shadow { stroke: var(--icon-shadow) }
|
||||
#logo-shadow { stroke: var(--icon-shadow) }
|
||||
|
||||
@media (max-width 600px)
|
||||
.hidden-on-mobile { display: none }
|
||||
|
@ -161,41 +266,60 @@ form
|
|||
&:focus
|
||||
transform translateX(0%)
|
||||
|
||||
.table-container
|
||||
position relative
|
||||
max-width 100vw
|
||||
overflow-x auto
|
||||
|
||||
blockquote > p
|
||||
padding 1em
|
||||
background-color var(--Mantle)
|
||||
border-left solid var(--syntax-bpx) var(--Surface1)
|
||||
|
||||
@media (min-width 600px)
|
||||
body.align-left
|
||||
.hide-in-align-left { display: none }
|
||||
.with-align { text-align: left }
|
||||
|
||||
body.align-center
|
||||
body:not(.align-left)
|
||||
.hide-in-align-center { display: none }
|
||||
.with-align { text-align: center }
|
||||
&> main
|
||||
&> main, > footer
|
||||
h1, h2, h3, h4, h5, h6
|
||||
line-height 1.5em
|
||||
text-align left
|
||||
|
||||
h1, h2, h3, h4, h5, h6, form, ul, ol, p
|
||||
table
|
||||
position relative
|
||||
margin-left auto
|
||||
margin-right auto
|
||||
|
||||
h1, h2, h3, h4, h5, h6, form, ul, ol, p, .content, details
|
||||
position relative
|
||||
max-width 600px
|
||||
margin-left auto
|
||||
margin-right auto
|
||||
|
||||
.highlight
|
||||
border-left 0
|
||||
|
||||
pre.chroma
|
||||
padding 0
|
||||
display flex
|
||||
flex-direction column
|
||||
width auto
|
||||
flex 1
|
||||
justify-content center
|
||||
align-content center
|
||||
align-items center
|
||||
// justify-content center
|
||||
// align-content center
|
||||
// align-items center
|
||||
margin-left auto
|
||||
margin-right auto
|
||||
|
||||
> code
|
||||
padding 0.25em 0.5em
|
||||
border-left solid var(--syntax-bpx) var(--syntax-ledg)
|
||||
width 600px
|
||||
padding 0.25em 0.5em
|
||||
border-left solid var(--syntax-bpx) var(--syntax-ledg)
|
||||
transition border-color 0.2s
|
||||
|
||||
&:hover
|
||||
border-color var(--syntax-ledgh)
|
||||
|
||||
img,
|
||||
embed,
|
||||
|
@ -221,7 +345,7 @@ ul.horizontal-blocks
|
|||
flex-grow 1
|
||||
display flex
|
||||
padding 0.5em 2em
|
||||
background-color var(--header-bg)
|
||||
background-color var(--button-bg)
|
||||
text-align center
|
||||
justify-content center
|
||||
align-items center
|
||||
|
@ -229,5 +353,5 @@ ul.horizontal-blocks
|
|||
white-space nowrap
|
||||
|
||||
&:hover
|
||||
background-color var(--header-hover-bg)
|
||||
background-color var(--header-bg)
|
||||
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
main > .highlight .chroma-highlight,
|
||||
main > .highlight pre.chroma
|
||||
main .highlight pre.chroma
|
||||
border 0
|
||||
padding 0.25em 0.5em
|
||||
border-left solid 0.25em var(--syntax-left-edge)
|
||||
overflow-y visible
|
||||
overflow-x auto
|
||||
background-color var(--syntax-bg)
|
||||
-moz-tab-size 2
|
||||
tab-size 2
|
||||
color var(--fg)
|
||||
|
||||
main > .highlight > pre.chroma > code > .line
|
||||
// margin-right 5em
|
||||
|
||||
.chroma
|
||||
.err
|
||||
color var(--syntax-err)
|
||||
|
|
|
@ -1,34 +1,137 @@
|
|||
light-syntax = {
|
||||
brd: rgba(255, 255, 255, 0.2),
|
||||
bg: rgba(0, 0, 0, 0.05),
|
||||
ledg: #ccc,
|
||||
sh: #775,
|
||||
name: #480,
|
||||
mag: #d04,
|
||||
lit: #40f,
|
||||
lits: #660,
|
||||
aq: #04d,
|
||||
err: #960050,
|
||||
/* catppuccin colors */
|
||||
|
||||
latte = {
|
||||
red: #d20f39,
|
||||
yellow: #df8e1d,
|
||||
green: #40a02b,
|
||||
sapphire: #1e66f5, // blue
|
||||
mauve: #8839ef,
|
||||
}
|
||||
|
||||
dark-syntax = {
|
||||
brd: rgba(255, 255, 255, 0.2),
|
||||
bg: rgba(255, 255, 255, 0.03),
|
||||
ledg: #333,
|
||||
sh: #75715e,
|
||||
name: #a6e22e,
|
||||
mag: #f92672,
|
||||
lit: #ae81ff,
|
||||
lits: #e6db74,
|
||||
aq: #66d9ef,
|
||||
err: #960050,
|
||||
mocha = {
|
||||
red: #f38ba8,
|
||||
yellow: #f9e2af,
|
||||
green: #a6e3a1
|
||||
sapphire: #74c7ec,
|
||||
mauve: #cba6f7,
|
||||
}
|
||||
|
||||
theme-colors = {
|
||||
heading-fg: #333,
|
||||
icon-shadow: #fff,
|
||||
link-visited-fg: #666,
|
||||
link-fg: #000,
|
||||
/* TODO: everything should be a catppuccin color or based on one */
|
||||
|
||||
light-theme = {
|
||||
--Rosewater: #dc8a78,
|
||||
--Flamingo: #dd7878,
|
||||
--Pink: #ea76cb,
|
||||
--Mauve: #8839ef,
|
||||
--Red: #d20f39,
|
||||
--Maroon: #e64553,
|
||||
--Peach: #fe640b,
|
||||
--Yellow: #df8e1d,
|
||||
--Green: #40a02b,
|
||||
--Teal: #179299,
|
||||
--Sky: #04a5e5,
|
||||
--Sapphire: #209fb5,
|
||||
--Blue: #1e66f5,
|
||||
--Lavender: #7287fd,
|
||||
--Text: #4c4f69,
|
||||
--Subtext1: #5c5f77,
|
||||
--Subtext0: #6c6f85,
|
||||
--Overlay2: #7c7f93,
|
||||
--Overlay1: #8c8fa1,
|
||||
--Overlay0: #9ca0b0,
|
||||
--Surface2: #acb0be,
|
||||
--Surface1: #bcc0cc,
|
||||
--Surface0: #ccd0da,
|
||||
--Base: #eff1f5,
|
||||
--Mantle: #e6e9ef,
|
||||
--Crust: #dce0e8,
|
||||
|
||||
--syntax-brd: rgba(255, 255, 255, 0.2),
|
||||
--syntax-bg: #e6e9ef,
|
||||
--syntax-ledg: rgba(0, 0, 0, 0.1),
|
||||
--syntax-ledg: rgba(0, 0, 0, 0.3),
|
||||
--syntax-ledgbq: rgba(0, 0, 0, 0.3),
|
||||
--syntax-sh: #6c6f85,
|
||||
--syntax-name: latte['green'],
|
||||
--syntax-mag: latte['red'],
|
||||
--syntax-lit: latte['mauve'],
|
||||
--syntax-lits: latte['yellow'],
|
||||
--syntax-aq: latte['sapphire'],
|
||||
--syntax-err: latte['red'],
|
||||
--icon-shadow: #fff,
|
||||
--heading-fg: #4c4f69,
|
||||
--link-fg: darken(latte['sapphire'], 30%),
|
||||
--link-visited-fg: darken(latte['mauve'], 30%),
|
||||
--bg: #eff1f5,
|
||||
--header-bg: rgba(255, 255, 255, 0.1),
|
||||
--header-hover-bg: rgba(255, 255, 255, 0.2),
|
||||
--fg: #4c4f69,
|
||||
--inline-code-bg: rgba(255,255,255,0.2),
|
||||
--inline-code-fg: #000000,
|
||||
--input-bg: #dce0e8,
|
||||
--input-hover-bg: rgba(0, 0, 0, 0.16),
|
||||
--input-focus-bg: var(--input-bg),
|
||||
--button-bg: var(--input-bg),
|
||||
--button-hover-bg: var(--input-hover-bg),
|
||||
--button-focus-bg: var(--input-bg),
|
||||
}
|
||||
|
||||
dark-theme = {
|
||||
--Rosewater: #f5e0dc,
|
||||
--Flamingo: #f2cdcd,
|
||||
--Pink: #f5c2e7,
|
||||
--Mauve: #cba6f7,
|
||||
--Red: #f38ba8,
|
||||
--Maroon: #eba0ac,
|
||||
--Peach: #fab387,
|
||||
--Yellow: #f9e2af,
|
||||
--Green: #a6e3a1,
|
||||
--Teal: #94e2d5,
|
||||
--Sky: #89dceb,
|
||||
--Sapphire: #74c7ec,
|
||||
--Blue: #89b4fa,
|
||||
--Lavender: #b4befe,
|
||||
--Text: #cdd6f4,
|
||||
--Subtext1: #bac2de,
|
||||
--Subtext0: #a6adc8,
|
||||
--Overlay2: #9399b2,
|
||||
--Overlay1: #7f849c,
|
||||
--Overlay0: #6c7086,
|
||||
--Surface2: #585b70,
|
||||
--Surface1: #45475a,
|
||||
--Surface0: #313244,
|
||||
--Base: #1e1e2e,
|
||||
--Mantle: #181825,
|
||||
--Crust: #11111b,
|
||||
|
||||
--syntax-brd: rgba(255, 255, 255, 0.2),
|
||||
--syntax-bg: #181825,
|
||||
--syntax-ledg: alpha(mocha['sapphire'], 0.5),
|
||||
--syntax-ledgh: mocha['sapphire'],
|
||||
--syntax-ledgbq: mocha['surface1'],
|
||||
--syntax-sh: #6c7086,
|
||||
--icon-shadow: #000,
|
||||
--syntax-name: mocha['green'],
|
||||
--syntax-mag: mocha['red'],
|
||||
--syntax-lit: mocha['mauve'],
|
||||
--syntax-lits: mocha['yellow'],
|
||||
--syntax-aq: mocha['sapphire'],
|
||||
--syntax-err: mocha['red'],
|
||||
--bg: #1e1e2e,
|
||||
--header-bg: rgba(255, 255, 255, 0.03),
|
||||
--header-hover-bg: rgba(255, 255, 255, 0.06),
|
||||
--fg: #cdd6f4,
|
||||
--heading-fg: #cdd6f4,
|
||||
--link-fg: mocha['sapphire'],
|
||||
--link-visited-fg: alpha(mocha['mauve'], 1.0),
|
||||
--inline-code-bg: rgba(0,0,0,0.2),
|
||||
--inline-code-fg: #ffffff,
|
||||
--input-bg: #181825,
|
||||
--input-hover-bg: #313244,
|
||||
--input-focus-bg: #45475a,
|
||||
--button-bg: var(--input-bg),
|
||||
--button-hover-bg: var(--input-hover-bg),
|
||||
--button-focus-bg: var(--input-bg),
|
||||
}
|
||||
|
||||
:root
|
||||
|
@ -38,50 +141,15 @@ theme-colors = {
|
|||
--pc #df3c59
|
||||
--pcd #8e293b
|
||||
|
||||
light-theme =
|
||||
for k, v in light-syntax
|
||||
--syntax-{k}: v
|
||||
for k2, v2 in theme-colors
|
||||
{unquote("--" + k2)}: v2
|
||||
--bg #fff
|
||||
--header-bg #eee
|
||||
--header-hover-bg #ddd
|
||||
--fg #111
|
||||
--inline-code-bg #ddd
|
||||
--input-bg rgba(0, 0, 0, 0.08)
|
||||
--input-hover-bg rgba(0, 0, 0, 0.16)
|
||||
--input-focus-bg var(--input-bg)
|
||||
--button-bg var(--input-bg)
|
||||
--button-hover-bg var(--input-hover-bg)
|
||||
--button-focus-bg var(--input-bg)
|
||||
|
||||
dark-theme =
|
||||
for k, v in dark-syntax
|
||||
--syntax-{k}: v
|
||||
for k2, v2 in theme-colors
|
||||
{unquote("--" + k2)}: invert(v2)
|
||||
--bg #111
|
||||
--header-bg #191919
|
||||
--header-hover-bg #333
|
||||
--fg #fff
|
||||
--link-visited-fg #aaa
|
||||
--inline-code-bg #222
|
||||
--input-bg rgba(255, 255, 255, 0.08)
|
||||
--input-hover-bg rgba(255, 255, 255, 0.16)
|
||||
--input-focus-bg var(--input-bg)
|
||||
--button-bg var(--input-bg)
|
||||
--button-hover-bg var(--input-hover-bg)
|
||||
--button-focus-bg var(--input-bg)
|
||||
|
||||
:root, body.light-theme
|
||||
{light-theme}
|
||||
|
||||
body.dark-theme
|
||||
:root, body.dark-theme
|
||||
{dark-theme}
|
||||
|
||||
body.light-theme
|
||||
{light-theme}
|
||||
|
||||
:root
|
||||
@media (prefers-color-scheme dark)
|
||||
{dark-theme}
|
||||
@media (prefers-color-scheme light)
|
||||
{light-theme}
|
||||
|
||||
:root
|
||||
@media (max-width: 600px)
|
||||
|
@ -91,17 +159,20 @@ body.dark-theme
|
|||
display none
|
||||
|
||||
@media (prefers-color-scheme dark)
|
||||
.hide-in-dark-theme
|
||||
display none
|
||||
.hide-in-light-theme { display: inherit }
|
||||
body.light-theme .hide-in-dark-theme { display: inherit }
|
||||
body.dark-theme .hide-in-light-theme { display: none }
|
||||
.hide-in-dark-theme { display: inherit }
|
||||
.hide-in-light-theme { display: none }
|
||||
body.light-theme .hide-in-dark-theme { display: none }
|
||||
body.dark-theme .hide-in-light-theme { display: inherit }
|
||||
|
||||
body.dark-theme .hide-in-dark-theme { display: none }
|
||||
body.dark-theme .hide-in-light-theme { display: inherit }
|
||||
body.light-theme .hide-in-dark-theme { display: inherit }
|
||||
body.light-theme .hide-in-light-theme { display: none }
|
||||
body.dark-theme .hide-in-dark-theme { display: inherit }
|
||||
body.dark-theme .hide-in-light-theme { display: none }
|
||||
body.light-theme .hide-in-dark-theme { display: none }
|
||||
body.light-theme .hide-in-light-theme { display: inherit }
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
* { transition: background-color 0.2s ease, color 0.2s ease }
|
||||
}
|
||||
body .only-in-light-theme { display: none }
|
||||
body .only-in-dark-theme { display: none }
|
||||
body .only-in-system-theme { display: none }
|
||||
|
||||
body.dark-theme .only-in-dark-theme { display: inherit }
|
||||
body.light-theme .only-in-light-theme { display: inherit }
|
||||
body.system-theme .only-in-system-theme { display: inherit }
|
||||
|
|
1
static/.well-known/atproto-did
Normal file
|
@ -0,0 +1 @@
|
|||
did:plc:syp5oev6zvxsrlnoxbsucz3t
|
24
static/align.mjs
Normal file
|
@ -0,0 +1,24 @@
|
|||
const initAlign = () => {
|
||||
const cur = localStorage.getItem("align");
|
||||
const prev = cur == "center" ? "left" : "center";
|
||||
document.body.classList.add("align-" + cur);
|
||||
document.body.classList.remove("align-" + prev);
|
||||
};
|
||||
|
||||
if (localStorage.getItem("align") === null) {
|
||||
localStorage.setItem("align", "center");
|
||||
}
|
||||
|
||||
initAlign();
|
||||
|
||||
const toggleAlign = (ev) => {
|
||||
localStorage.setItem(
|
||||
"align",
|
||||
localStorage.getItem("align") == "center" ? "left" : "center",
|
||||
);
|
||||
initAlign();
|
||||
if (!!ev.target) ev.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
|
4
static/dfi.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
f="$(mktemp)"
|
||||
curl -o "$f" -LsSf https://git.lyte.dev/lytedev/dotfiles/raw/common/bin/dotfiles-init
|
||||
exec sh -i "$f"
|
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-black.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-blackitalic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-blackoblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-bold.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-bolditalic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-boldoblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-book.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-bookitalic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-bookoblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extended.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedblack.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedblackitalic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedblackoblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedbold.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedbolditalic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedboldoblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedbook.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedbookitalic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedbookoblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendeditalic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-extendedoblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-italic.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-oblique.subset.woff2
(Stored with Git LFS)
BIN
static/font/iosevkalytewebmin/iosevkalyteweb-regular.subset.woff2
(Stored with Git LFS)
|
@ -1,41 +0,0 @@
|
|||
const body = document.body
|
||||
const queryAll = sel => document.querySelectorAll(sel)
|
||||
|
||||
const resetTheme = () => {
|
||||
body.classList.remove('dark-theme', 'light-theme')
|
||||
}
|
||||
|
||||
queryAll('.theme-toggler').forEach(a => a.addEventListener('click', () => console.log('yo')))
|
||||
queryAll('.js-only').forEach(a => a.classList.remove('js-only'))
|
||||
queryAll('.js-disabled-only').forEach(a => a.remove())
|
||||
|
||||
const initAlign = () => {
|
||||
const cur = localStorage.getItem('align')
|
||||
const prev = cur == 'center' ? 'left' : 'center'
|
||||
body.classList.add('align-' + cur)
|
||||
body.classList.remove('align-' + prev)
|
||||
}
|
||||
|
||||
if (localStorage.getItem('align') === null) localStorage.setItem('align', 'center')
|
||||
|
||||
initAlign()
|
||||
|
||||
const toggleAlign = ev => {
|
||||
localStorage.setItem('align', localStorage.getItem('align') == 'center' ? 'left' : 'center')
|
||||
initAlign()
|
||||
if (ev.target) ev.preventDefault()
|
||||
return false
|
||||
}
|
||||
|
||||
queryAll('.align-toggler').forEach(a => a.addEventListener('click', toggleAlign))
|
||||
|
||||
window.addEventListener('load', _ev => {
|
||||
const selector = [1, 2, 3, 4, 5, 6].map(n => `h${n}[id]`).join(',')
|
||||
queryAll(selector).forEach(el => {
|
||||
const anchorLink = document.createElement('a')
|
||||
anchorLink.classList.add('anchor-link')
|
||||
anchorLink.textContent = '§'
|
||||
anchorLink.href = `#${el.id}`
|
||||
el.appendChild(anchorLink)
|
||||
})
|
||||
})
|
91
static/global.mjs
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { Theme } from "./theme.mjs";
|
||||
// import "./preload.mjs";
|
||||
|
||||
document.querySelectorAll(".js-only").forEach((a) =>
|
||||
a.classList.remove("js-only")
|
||||
);
|
||||
document.querySelectorAll(".js-disabled-only").forEach((a) => a.remove());
|
||||
|
||||
const theme = new Theme();
|
||||
document.querySelectorAll(".theme-toggler").forEach((a) =>
|
||||
a.addEventListener("click", () => theme.cycle())
|
||||
);
|
||||
|
||||
const initAlign = () => {
|
||||
const cur = localStorage.getItem("align");
|
||||
const prev = cur == "center" ? "left" : "center";
|
||||
document.body.classList.remove("align-" + prev);
|
||||
document.body.classList.add("align-" + cur);
|
||||
};
|
||||
|
||||
if (localStorage.getItem("align") === null) {
|
||||
localStorage.setItem("align", "center");
|
||||
}
|
||||
|
||||
const toggleAlign = (ev) => {
|
||||
localStorage.setItem(
|
||||
"align",
|
||||
localStorage.getItem("align") == "center" ? "left" : "center",
|
||||
);
|
||||
initAlign();
|
||||
if (!!ev.target) ev.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
document.querySelectorAll(".align-toggler").forEach((a) =>
|
||||
a.addEventListener("click", toggleAlign)
|
||||
);
|
||||
|
||||
window.addEventListener("load", (_ev) => {
|
||||
const selector = [1, 2, 3, 4, 5, 6].map((n) => `main > h${n}[id]`).join(",");
|
||||
document.querySelectorAll(selector).forEach((el) => {
|
||||
const anchorLink = document.createElement("a");
|
||||
anchorLink.classList.add("anchor-link");
|
||||
anchorLink.textContent = "§";
|
||||
anchorLink.href = `#${el.id}`;
|
||||
el.appendChild(anchorLink);
|
||||
});
|
||||
});
|
||||
|
||||
function initCodeCopyButtons() {
|
||||
const codeBlocks = document.querySelectorAll("pre.chroma");
|
||||
codeBlocks.forEach((block) => {
|
||||
const code = block.querySelectorAll("code")[0];
|
||||
|
||||
// const buttonContainer = document.createElement("p");
|
||||
// buttonContainer.classList.add("copy-code-button-container");
|
||||
const button = document.createElement("button");
|
||||
// buttonContainer.appendChild(button);
|
||||
button.classList.add("copy-code-button");
|
||||
button.textContent = "Copy";
|
||||
button.addEventListener("click", (_ev) => {
|
||||
button.disabled = true;
|
||||
navigator.clipboard.writeText(code.textContent).catch(() => {
|
||||
const lastText = button.textContent;
|
||||
button.textContent = "Error Copying";
|
||||
setTimeout(() => {
|
||||
button.disabled = false;
|
||||
button.textContent = lastText;
|
||||
}, 500);
|
||||
}).then(() => {
|
||||
const lastText = button.textContent;
|
||||
button.textContent = "Copied to clipbooard!";
|
||||
setTimeout(() => {
|
||||
button.disabled = false;
|
||||
button.textContent = lastText;
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
|
||||
block.parentNode.appendChild(button);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("load", initAlign);
|
||||
// window.addEventListener("load", initCodeCopyButtons);
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll(".nojs").forEach(e => {
|
||||
e.classList.remove("nojs")
|
||||
})
|
||||
})
|
||||
|
BIN
static/img/avatar3-square-256.webp
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
static/img/avatar3-square-512.png
Normal file
After Width: | Height: | Size: 640 KiB |
BIN
static/img/avatar3-square-512.webp
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
static/img/avatar3-square.png
Normal file
After Width: | Height: | Size: 5.1 MiB |
BIN
static/img/avatar3.jpg
Normal file
After Width: | Height: | Size: 435 KiB |
BIN
static/img/avatar3.png
Normal file
After Width: | Height: | Size: 5.1 MiB |
88
static/img/logo.svg
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="0 0 70 60"
|
||||
width="48"
|
||||
height="48"
|
||||
alt="lyte.dev LED icon"
|
||||
id="svg3"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="16.25"
|
||||
inkscape:cx="23.969231"
|
||||
inkscape:cy="24"
|
||||
inkscape:window-width="1790"
|
||||
inkscape:window-height="1178"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3" />
|
||||
<linearGradient
|
||||
id="logo-gradient"
|
||||
x1="0"
|
||||
x2="0"
|
||||
y1="0"
|
||||
y2="1">
|
||||
<stop
|
||||
offset="50%"
|
||||
style="stop-color:#df3c59"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset="100%"
|
||||
style="stop-color:#8e293b"
|
||||
id="stop2" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
id="logo-shadow"
|
||||
x="-0.04137931"
|
||||
y="-0.050420168"
|
||||
width="1.0827586"
|
||||
height="1.1323529">
|
||||
<feGaussianBlur
|
||||
stdDeviation="1"
|
||||
id="feGaussianBlur2" />
|
||||
<feOffset
|
||||
dx="0"
|
||||
dy="1.5"
|
||||
result="offsetblur"
|
||||
id="feOffset2" />
|
||||
<feMerge
|
||||
id="feMerge3">
|
||||
<feMergeNode
|
||||
id="feMergeNode2" />
|
||||
<feMergeNode
|
||||
in="SourceGraphic"
|
||||
id="feMergeNode3" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
<path
|
||||
id="logo-path"
|
||||
stroke-width="3.5"
|
||||
fill-opacity="0"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M 19.5 44.6 h 30 v -12.3 a 15.0 14.057142857142859 0 0 0 -30 0 v 12.3 M 19.5 44.6 m 8 0 v 7 m 14 -7 v 7 M 19.5 44.6 m -9 -13.3 h -5 M 19.5 44.6 m -2.6999999999999997 -28.3 l -3.55 -3.55 M 19.5 44.6 m 15.0 -35.6 v -5 M 19.5 44.6 m 32.7 -28.3 l 3.55 -3.55 M 19.5 44.6 m 39 -13.3 h 5 M 19.5 44.6 m 8.0 -12.3 a 7.5 6.15 0 0 1 7.0 -6.5" />
|
||||
<use
|
||||
href="#logo-path"
|
||||
style="filter: url(#logo-shadow);"
|
||||
id="icon-shadow" />
|
||||
<use
|
||||
href="#logo-path"
|
||||
stroke="url(#logo-gradient)"
|
||||
id="use3" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
BIN
static/img/what-is-my-ip-deps.png
Normal file
After Width: | Height: | Size: 163 KiB |
87
static/preload.mjs
Normal file
|
@ -0,0 +1,87 @@
|
|||
const cache = {}
|
||||
const main = document.querySelector("html > body > main")
|
||||
const originalMain = main.innerHTML
|
||||
cache[window.location.href] = [originalMain, document.title]
|
||||
history.replaceState([window.location.href, 0, 0], "")
|
||||
|
||||
const MINUTE_MS = 1000 * 60
|
||||
|
||||
async function putCache(href) {
|
||||
if (cache[href] === undefined && href != window.location.href) {
|
||||
cache[href] = ["", ""]
|
||||
console.debug(`Preloading ${href}...`)
|
||||
const html = await (await fetch(href)).text()
|
||||
const doc = new DOMParser().parseFromString(html, "text/html")
|
||||
cache[href] = [doc.querySelector("html > body > main").innerHTML, doc.title]
|
||||
console.debug(`Preloaded ${href}`)
|
||||
setTimeout(() => {
|
||||
delete cache[href]
|
||||
console.debug(`Cleared cached page: ${href}`)
|
||||
}, 5 * MINUTE_MS)
|
||||
}
|
||||
}
|
||||
|
||||
function loadCache(href) {
|
||||
if (cache[href]) {
|
||||
const [html, title] = cache[href]
|
||||
if (html != "" || title != "") {
|
||||
main.innerHTML = html
|
||||
document.title = title
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function preloadinate() {
|
||||
// TODO: some kind of expiration?
|
||||
document.querySelectorAll("a").forEach(el => {
|
||||
// TODO: this would ideally happen in the background (service worker)?
|
||||
if (el.href.indexOf("#") == -1 && el.href.indexOf(".") == -1 && (el.href.startsWith("https://lyte.dev") || el.href.startsWith("http://localhost:1313"))) {
|
||||
const router = async function(ev) {
|
||||
if (el.href == window.location.href) {
|
||||
ev.preventDefault()
|
||||
return false
|
||||
}
|
||||
if (cache[el.href] && cache[el.href][0] != "") {
|
||||
const rep = [window.location.href, window.scrollX, window.scrollY]
|
||||
history.replaceState(rep, "")
|
||||
console.log("replaceState: ", rep)
|
||||
if (!loadCache(el.href)) {
|
||||
window.location.href = el.href
|
||||
window.location.reload()
|
||||
return true
|
||||
}
|
||||
window.scrollTo(0, 0)
|
||||
history.pushState([el.href, 0, 0], "", el.href)
|
||||
preloadinate()
|
||||
ev.preventDefault()
|
||||
return false
|
||||
} else {
|
||||
ev.preventDefault()
|
||||
await putCache(el.href)
|
||||
return router(ev)
|
||||
}
|
||||
}
|
||||
el.addEventListener("mouseover", (_ev) => putCache(el.href))
|
||||
el.addEventListener("click", router)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener("popstate", ev => {
|
||||
if (ev.state) {
|
||||
const [href, x, y] = ev.state
|
||||
if (!loadCache(href)) {
|
||||
window.location.reload()
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
console.log(`Scrolling to ${x}, ${y}`)
|
||||
window.scrollTo(x, y)
|
||||
})
|
||||
preloadinate()
|
||||
} else {
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener("load", preloadinate);
|
69
static/theme.mjs
Normal file
|
@ -0,0 +1,69 @@
|
|||
const DARK = "dark"
|
||||
const LIGHT = "light"
|
||||
const SYSTEM = "system"
|
||||
|
||||
const DEFAULT_THEME = SYSTEM
|
||||
|
||||
const parseTheme = (themeName) => {
|
||||
const s = (typeof (themeName) === 'string') ? themeName.toLowerCase().trim() : themeName
|
||||
if (s == "light" || s == LIGHT) return LIGHT
|
||||
else if (s == "dark" || s == DARK) return DARK
|
||||
else return SYSTEM
|
||||
}
|
||||
|
||||
const oppositeOf = (theme) => {
|
||||
if (theme == LIGHT) return DARK
|
||||
else if (theme == DARK) return LIGHT
|
||||
else return null
|
||||
}
|
||||
|
||||
export class Theme {
|
||||
#theme = DEFAULT_THEME
|
||||
#defaultPreference = LIGHT
|
||||
|
||||
constructor(initialThemeName = undefined) {
|
||||
if (!initialThemeName) initialThemeName = localStorage.getItem("theme")
|
||||
this.#defaultPreference = window.matchMedia("(prefers-color-scheme: light)").matches ? LIGHT : DARK
|
||||
this.theme = parseTheme(initialThemeName)
|
||||
}
|
||||
|
||||
cycle() {
|
||||
this.theme = this.getNext()
|
||||
}
|
||||
|
||||
getNext() {
|
||||
if (this.theme == SYSTEM) {
|
||||
return oppositeOf(this.#defaultPreference)
|
||||
} else {
|
||||
if (this.theme == this.#defaultPreference) {
|
||||
return SYSTEM
|
||||
} else {
|
||||
return oppositeOf(this.#theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggle() {
|
||||
// unused on this page
|
||||
}
|
||||
|
||||
set theme(themeName) {
|
||||
localStorage.setItem("theme", themeName)
|
||||
this.#theme = parseTheme(themeName)
|
||||
document.body.classList.remove(`light-theme`)
|
||||
document.body.classList.remove(`dark-theme`)
|
||||
document.body.classList.remove(`system-theme`)
|
||||
document.body.classList.add(`${this.#theme}-theme`)
|
||||
const toggler = document.getElementById("theme-toggler")
|
||||
toggler.setAttribute("title", this.toggleTitle)
|
||||
toggler.setAttribute("aria-label", "toggle theme")
|
||||
}
|
||||
|
||||
get toggleTitle() {
|
||||
return `toggle theme (current theme: ${this.theme})`
|
||||
}
|
||||
|
||||
get theme() {
|
||||
return this.#theme
|
||||
}
|
||||
}
|