summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/blog/.gitignore2
-rw-r--r--packages/blog/config.toml8
-rw-r--r--packages/blog/content/posts/_index.md6
-rw-r--r--packages/blog/content/posts/deterministic-hostnames.md80
-rw-r--r--packages/blog/content/posts/nohup.md26
-rw-r--r--packages/blog/content/posts/stateless-compute-networks.md41
-rw-r--r--packages/blog/content/posts/vim-compilers.md20
-rw-r--r--packages/blog/default.nix21
-rw-r--r--packages/blog/templates/404.html9
-rw-r--r--packages/blog/templates/base.html88
-rw-r--r--packages/blog/templates/index.html16
-rw-r--r--packages/blog/templates/post.html14
-rw-r--r--packages/cses/default.nix13
-rw-r--r--packages/cses/problems/1068.cpp17
-rw-r--r--packages/cses/problems/1069.cpp23
-rw-r--r--packages/cses/problems/1070.cpp15
-rw-r--r--packages/cses/problems/1071.cpp19
-rw-r--r--packages/cses/problems/1083.cpp20
-rw-r--r--packages/cses/problems/1094.cpp27
-rw-r--r--packages/cv/cv.typ179
-rw-r--r--packages/cv/default.nix5
-rw-r--r--packages/perf-flamegraph.nix12
-rw-r--r--packages/resistors/default.nix7
-rw-r--r--packages/resistors/index.html339
-rw-r--r--packages/resistors/styles.css127
25 files changed, 1134 insertions, 0 deletions
diff --git a/packages/blog/.gitignore b/packages/blog/.gitignore
new file mode 100644
index 0000000..decc3f8
--- /dev/null
+++ b/packages/blog/.gitignore
@@ -0,0 +1,2 @@
+public/
+nohup.out
diff --git a/packages/blog/config.toml b/packages/blog/config.toml
new file mode 100644
index 0000000..69c389b
--- /dev/null
+++ b/packages/blog/config.toml
@@ -0,0 +1,8 @@
+base_url = "https://4kb.net"
+build_search_index = false
+compile_sass = false
+generate_feeds = true
+minify_html = true
+
+[markdown]
+highlight_code = false
diff --git a/packages/blog/content/posts/_index.md b/packages/blog/content/posts/_index.md
new file mode 100644
index 0000000..9efc62e
--- /dev/null
+++ b/packages/blog/content/posts/_index.md
@@ -0,0 +1,6 @@
++++
+title = "Posts"
+sort_by = "date"
+page_template = "post.html"
+redirect_to="/"
++++
diff --git a/packages/blog/content/posts/deterministic-hostnames.md b/packages/blog/content/posts/deterministic-hostnames.md
new file mode 100644
index 0000000..43dc7d8
--- /dev/null
+++ b/packages/blog/content/posts/deterministic-hostnames.md
@@ -0,0 +1,80 @@
+---
+title: "Deterministic and unique network hostnames"
+date: "2024-11-10"
+---
+
+As part of building out a Kubernetes cluster, I wanted to build and distribute a
+single OS image to create stateless worker nodes. Using network booting, and
+some clever tricks to differentiate nodes, we can create a scaleable and
+efficient farm of workers for a cluster that don't even need disks.
+
+The idea came from a plan to build a cluster using the
+[compute blade](https://computeblade.com/), and a few Raspberry Pi SBCs I
+already own. Running the cluster from an SD card is not recommended due to the
+not-so-great reliability of the flash used by most manufacturers, so I wanted to
+try PXE booting each Pi to save money rather than purchasing an SSD for each
+one. The compute blades do support an NVMe disk, but I plan to use those for a
+storage cluster later, so they need to remain empty.
+
+## Base image
+
+Alpine Linux has been my preferred server OS for a long time. It provides a very
+lightweight base system, and bundles an excellent bootstrapping system,
+[apkovl](https://wiki.alpinelinux.org/wiki/Alpine_local_backup), that allows the
+user to save a set of customisations to an system as an overlay to a stock
+Alpine live image. In other words, we can create our image once, save the
+changes as an `apkovl.tar.gz` file, and apply the same changes to a base system
+on boot. This file can even be provided as a
+[kernel parameter](https://wiki.alpinelinux.org/wiki/PXE_boot#Guide_to_options)
+and will be fetched from a remote webserver automatically!
+
+Since the image and configuration will be shipped to the node via the network,
+an added benefit of using Alpine is its tiny space consumption. I'm not using
+enough nodes for this to really matter, but it's a cool optimization regardless.
+
+## Differentiating the nodes
+
+One of the main goals of this project is that there should be no persistent
+storage required outside the boot image itself. Since every node will download
+and generate the same root file-system on startup, the first problem that arises
+is how the nodes will identify themselves both on the network and the cluster,
+given that it's not possible to name them ahead of time. In other words, any
+given node has to generate a unique hostname that won't collide with other
+workers, and that will be the same each time that node boots.
+
+Since these nodes will not have a predefined name, we have to rely on
+characteristics of the hardware to differentiate each one. The hardware MAC
+address is perfect for this, since it's unique to to each node and will not be
+wiped away after the node reboots. On a system like Linux that exposes its
+hardware through a _sysfs_, we can find a file containing the address at
+`/sys/class/net/eth0/address`. I don't really like the idea of attaching the
+literal MAC address of the node to its network hostname, since it's a security
+risk, and a bit too verbose. Instead, we can transform it into something safer
+using a `sha1sum`, which is already present on our Alpine base system:
+
+```console
+sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}'
+```
+
+### Applying the new name
+
+Ideally, the node should apply its generated hostname before reaching out for an
+address over DHCP or joining the cluster. We can make sure it happens before any
+traffic is sent out by adding a `pre-up` command to the right interface in
+`/etc/network/interfaces`:
+
+```
+
+...
+
+auto eth0
+iface eth0 inet dhcp
+ pre-up sha1sum /sys/class/net/eth0/address | head -c 6 | awk '{print "worker-" $0}' > /etc/hostname
+
+...
+```
+
+The VM I tested with looks outputs `worker-e2fae8`. Pretty clean result, and if
+you want to know the physical node that maps to each hostname, you can take note
+of the MAC address beforehand and generate the same hash on another computer to
+match them up.
diff --git a/packages/blog/content/posts/nohup.md b/packages/blog/content/posts/nohup.md
new file mode 100644
index 0000000..64f7983
--- /dev/null
+++ b/packages/blog/content/posts/nohup.md
@@ -0,0 +1,26 @@
+---
+title: "Spawning background processes"
+date: "2024-11-17"
+---
+
+Working in a terminal,
+I often pair my editor with a background process watching files.
+Before reaching for terminal multiplexers,
+see if you can get away with simple tty job control.
+Spawn the background process,
+still attached to the terminal instance:
+
+```
+program args &
+```
+
+Also redirect its output to a file,
+for when the process writes to the tty from the background:
+
+```
+nohup program args &
+```
+
+Extra reading:
+
+- <https://jvns.ca/blog/2024/07/03/reasons-to-use-job-control/>
diff --git a/packages/blog/content/posts/stateless-compute-networks.md b/packages/blog/content/posts/stateless-compute-networks.md
new file mode 100644
index 0000000..bac9e5d
--- /dev/null
+++ b/packages/blog/content/posts/stateless-compute-networks.md
@@ -0,0 +1,41 @@
+---
+title: "On stateless compute networks"
+date: "2024-11-10"
+draft: true
+---
+
+On the topic of distributed systems and clustering,
+I am quite invested in the idea of compute nodes that rely entirely on the network for configuration.
+Arbitrary nodes can join a pre-existing cluster,
+offering their CPU time and memory for computation without relying on any pre-existing configuration on the node itself.
+In other words, any computer could pick up work,
+only needing power and a network connection to the cluster.
+
+Perhaps this eventually leads into a "self-healing" cluster where only one node is manually bootstrapped,
+which then serves a _configuration endpoint_ for other stateless nodes to reach out to for their instructions,
+which they will then also serve once they are themselves ready.
+
+Early revisions of these notes mention Kubernetes,
+but I am also trying to achieve similar results with NixOS on a custom project.
+In any case, these are my ever-updating notes towards a general implementation of a stateless distributed systems architecture.
+
+## Self healing cluster
+
+Assuming control of an external DHCP server,
+a self healing Kubernetes cluster would be feasible,
+with the PXE boot artifacts supplied by the cluster itself.
+That is, as long as one node is running the pod hosting the artifacts on a given endpoint,
+other nodes can boot those artifacts and join the cluster,
+thereby being able to host the artifacts as well.
+
+## Configuration endpoint
+
+The nodes shouldn't require a disk installed to be able to join the network.
+Rather, the lofty goal of zero-configuration compute nodes passes the job of node initialization to the supporting network.
+This is accomplished with PXE boot instructions supplied over DHCP.
+
+I delegate the following tasks to a single node in the subnet:
+
+- Gateway: Optional outbound connections if required
+- DHCP server: Cluster IPAM
+- TFTP and HTTP server: Serves iPXE firmware and kernel/initrd artifacts
diff --git a/packages/blog/content/posts/vim-compilers.md b/packages/blog/content/posts/vim-compilers.md
new file mode 100644
index 0000000..f735228
--- /dev/null
+++ b/packages/blog/content/posts/vim-compilers.md
@@ -0,0 +1,20 @@
+---
+title: "Neovim's built-in compilers"
+date: "2025-01-17"
+---
+
+Today I learned that neovim's `:make` comes with many
+[backends](https://neovim.io/doc/user/quickfix.html#_6.-selecting-a-compiler)
+already configured.
+This works for checking c/cpp and python files, among other, but I was
+most interested to see [Typst] and [Pandoc] listed as well.
+
+I was looking to add this functionality with a plugin or implementing it
+manually,
+where a document can be compiled on the fly from the editor.
+After setting `:compiler pandoc`,
+generating a pdf is done with `:make pdf`,
+with other pandoc options just appended afterwards if needed.
+
+[typst]: https://typst.app
+[pandoc]: https://github.com/jgm/pandoc
diff --git a/packages/blog/default.nix b/packages/blog/default.nix
new file mode 100644
index 0000000..204388f
--- /dev/null
+++ b/packages/blog/default.nix
@@ -0,0 +1,21 @@
+{ pkgs, perSystem, ... }:
+let
+ inherit (perSystem.self)
+ cv
+ ;
+in
+pkgs.stdenvNoCC.mkDerivation {
+ name = "4kb.net";
+ src = ./.;
+
+ nativeBuildInputs = with pkgs; [
+ zola
+ ];
+
+ buildPhase = "zola build";
+ installPhase = ''
+ mkdir -p $out/share
+ cp -r public/* $out/
+ cp ${cv} $out/share/cv.pdf
+ '';
+}
diff --git a/packages/blog/templates/404.html b/packages/blog/templates/404.html
new file mode 100644
index 0000000..a4669df
--- /dev/null
+++ b/packages/blog/templates/404.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+
+{% block content %}
+<head>
+ <title>Error 404!</title>
+</head>
+
+404!
+{% endblock content %}
diff --git a/packages/blog/templates/base.html b/packages/blog/templates/base.html
new file mode 100644
index 0000000..5a91df5
--- /dev/null
+++ b/packages/blog/templates/base.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>{% block title %}4kb.net{% endblock title %}</title>
+ </head>
+ <style>
+ :root {
+ --bg: #181616;
+ --fg: #c5c9c5;
+ --link: #76946a;
+ --linkhover: #98bb6c;
+ }
+
+ html {
+ scroll-behavior: smooth;
+ color-scheme: dark;
+ }
+
+ body {
+ font-family: serif;
+ padding: 0 0.75em;
+ max-width: 42em;
+ margin: auto;
+ background: var(--bg);
+ color: var(--fg);
+ }
+
+ p {
+ line-height: 1.3em;
+ }
+
+ th,
+ td {
+ padding: 0.2em 0.4em;
+ border: thin solid;
+ }
+
+ table {
+ border: thin solid;
+ border-collapse: collapse;
+ }
+
+ a,
+ a:link {
+ color: var(--link);
+ }
+
+ a:hover {
+ color: var(--linkhover);
+ }
+
+ article img {
+ display: block;
+ margin: 0 auto;
+ max-width: 80%;
+ height: auto;
+ object-fit: contain;
+ }
+
+ pre {
+ padding: 1em;
+ overflow-x: scroll;
+ }
+ </style>
+ <body>
+ <header
+ style="
+ margin: 3em 0 3em 0;
+ display: flex;
+ gap: 8px;
+ flex-direction: column;
+ font-family: monospace;
+ "
+ >
+ <span
+ >Kleidi Bujari <<a href="mailto:mail@4kb.net">mail@4kb.net</a>></span
+ >
+ <nav style="display: flex; gap: 8px">
+ <span><a href="/">4kb.net</a> |</span>
+ <a target="_blank" href="https://github.com/kbujari">git</a>
+ <a href="/atom.xml">rss</a>
+ </nav>
+ </header>
+ <main>{% block content %} {% endblock %}</main>
+ </body>
+</html>
diff --git a/packages/blog/templates/index.html b/packages/blog/templates/index.html
new file mode 100644
index 0000000..6e8a2af
--- /dev/null
+++ b/packages/blog/templates/index.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %} {% block content %}
+
+<div>
+ {% set section = get_section(path="posts/_index.md") %}
+
+ <ul style="list-style: none; padding-left: 0">
+ {% for page in section.pages %}
+ <li style="display: flex; gap: 1em; margin-bottom: 0.3em">
+ <span sty>{{ page.date }}</span>
+ <a href="{{ page.permalink | safe }}">{{ page.title }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+
+ {% endblock content %}
+</div>
diff --git a/packages/blog/templates/post.html b/packages/blog/templates/post.html
new file mode 100644
index 0000000..b819520
--- /dev/null
+++ b/packages/blog/templates/post.html
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+
+{% block title%}
+ {{ page.title }} - {{ super() }}
+{% endblock title %}
+
+{% block content %}
+
+<h1 style="text-align: center">{{ page.title }}</h1>
+<article>
+ {{ page.content | safe }}
+</article>
+
+{% endblock content %}
diff --git a/packages/cses/default.nix b/packages/cses/default.nix
new file mode 100644
index 0000000..6538eb8
--- /dev/null
+++ b/packages/cses/default.nix
@@ -0,0 +1,13 @@
+{ pkgs ? import <nixpkgs> { }, ... }:
+
+pkgs.mkShell {
+ packages = with pkgs; [
+ clang-tools
+ (writeShellScriptBin "cpprun" ''
+ TEMP=".tmp.cpp"
+ g++ -std=c++20 -O3 ./problems/"$1.cpp" -o $TEMP
+ ./$TEMP
+ rm $TEMP
+ '')
+ ];
+}
diff --git a/packages/cses/problems/1068.cpp b/packages/cses/problems/1068.cpp
new file mode 100644
index 0000000..8d05936
--- /dev/null
+++ b/packages/cses/problems/1068.cpp
@@ -0,0 +1,17 @@
+#include <iostream>
+
+int main(int argc, char *argv[]) {
+ unsigned long long n;
+ std::cin >> n;
+
+ while (n != 1) {
+ std::cout << n << " ";
+
+ if (n % 2 == 0)
+ n /= 2;
+ else
+ n = (n * 3) + 1;
+ }
+
+ std::cout << 1;
+}
diff --git a/packages/cses/problems/1069.cpp b/packages/cses/problems/1069.cpp
new file mode 100644
index 0000000..232d2f7
--- /dev/null
+++ b/packages/cses/problems/1069.cpp
@@ -0,0 +1,23 @@
+#include <iostream>
+
+int main() {
+ std::string input;
+ std::cin >> input;
+
+ auto max = 0;
+ auto curr = '-';
+ auto count = 0;
+
+ for (auto const ch : input) {
+ if (curr != ch) {
+ curr = ch;
+ count = 0;
+ }
+
+ count += 1;
+ max = std::max(max, count);
+ }
+
+ std::cout << max;
+ return 0;
+}
diff --git a/packages/cses/problems/1070.cpp b/packages/cses/problems/1070.cpp
new file mode 100644
index 0000000..131a644
--- /dev/null
+++ b/packages/cses/problems/1070.cpp
@@ -0,0 +1,15 @@
+#include <iostream>
+
+int main() {
+ int n;
+ std::cin >> n;
+
+ if (n == 1)
+ std::cout << 1;
+ else if (n < 4)
+ std::cout << "NO SOLUTION";
+ else {
+ for (auto i = 2; i <= n; i += 2) std::cout << i << " ";
+ for (auto i = 1; i <= n; i += 2) std::cout << i << " ";
+ }
+}
diff --git a/packages/cses/problems/1071.cpp b/packages/cses/problems/1071.cpp
new file mode 100644
index 0000000..778cc86
--- /dev/null
+++ b/packages/cses/problems/1071.cpp
@@ -0,0 +1,19 @@
+#include <iostream>
+#include <tuple>
+#include <vector>
+
+using ull = unsigned long long;
+
+int main() {
+ int n;
+ std::cin >> n;
+
+ while (n--) {
+ ull x, y;
+ std::cin >> y >> x;
+
+ auto area = y * y;
+ auto perimeter = y + y + 1;
+ auto max = area + perimeter;
+ }
+}
diff --git a/packages/cses/problems/1083.cpp b/packages/cses/problems/1083.cpp
new file mode 100644
index 0000000..a3cd253
--- /dev/null
+++ b/packages/cses/problems/1083.cpp
@@ -0,0 +1,20 @@
+#include <iostream>
+#include <set>
+
+using ull = unsigned long long;
+
+int main(int argc, char *argv[]) {
+ ull n;
+ std::cin >> n;
+
+ auto set = std::set<ull>{};
+
+ std::string numstr;
+ while (std::getline(std::cin, numstr, ' ')) set.insert(std::stoi(numstr));
+
+ for (auto i = 1; i <= n; i++) {
+ if (set.contains(i)) continue;
+ std::cout << i;
+ break;
+ }
+}
diff --git a/packages/cses/problems/1094.cpp b/packages/cses/problems/1094.cpp
new file mode 100644
index 0000000..56f3886
--- /dev/null
+++ b/packages/cses/problems/1094.cpp
@@ -0,0 +1,27 @@
+#include <iostream>
+#include <vector>
+
+using ull = unsigned long long;
+
+int main() {
+ ull n;
+ auto arr = std::vector<ull>{};
+
+ std::cin >> n;
+
+ std::string type;
+ while (std::cin >> type) arr.push_back(std::stoi(type));
+
+ ull count = 0;
+ auto last = arr[0];
+
+ for (auto const num : arr) {
+ if (num > last) {
+ last = num;
+ continue;
+ }
+ count += last - num;
+ }
+
+ std::cout << count;
+}
diff --git a/packages/cv/cv.typ b/packages/cv/cv.typ
new file mode 100644
index 0000000..7d43763
--- /dev/null
+++ b/packages/cv/cv.typ
@@ -0,0 +1,179 @@
+#set document(author: "Kleidi Bujari", title: "Kleidi's Resume!")
+#set text(size: 10pt, lang: "en", ligatures: false)
+#set page(margin: 0.5in, paper: "us-letter")
+
+#show link: underline
+#set par(justify: true)
+
+#show heading.where(level: 1): it => [
+ #set text(weight: 700, size: 20pt)
+ #pad(it.body)
+]
+
+#show heading.where(level: 2): it => [
+ #pad(top: 0pt, bottom: -10pt, [#smallcaps(it.body)])
+ #line(length: 100%, stroke: 1pt)
+]
+
+#let generic-two-by-two(
+ top-left: "",
+ top-right: "",
+ bottom-left: "",
+ bottom-right: "",
+) = [
+ #strong(top-left) #h(1fr) #top-right \
+ #bottom-left #h(1fr) #bottom-right
+]
+
+#let dates-helper(from: "", to: "") = from + " " + $dash.em$ + " " + to
+
+#let edu(
+ institution: "",
+ dates: "",
+ degree: "",
+ location: "",
+) = generic-two-by-two(
+ top-left: institution,
+ top-right: dates,
+ bottom-left: degree,
+ bottom-right: location,
+)
+
+#let work(
+ title: "",
+ dates: "",
+ company: "",
+ location: "",
+) = generic-two-by-two(
+ top-left: title,
+ top-right: dates,
+ bottom-left: company,
+ bottom-right: location,
+)
+
+= Kleidi Bujari
+
+#(
+ link("mailto:mail@4kb.net"),
+ link("https://github.com/kbujari")[github.com/kbujari],
+ link("http://4kb.net")[4kb.net],
+).join(" | ")
+
+== Experience
+
+#work(
+ company: "Meta",
+ title: "Production Engineer",
+ dates: dates-helper(from: "Jul 2024", to: "Present"),
+ location: "Menlo Park",
+)
+
+- Worked on Linux kernel and Erlang's BEAM virtual machine,
+ shipping performance optimizations directly to upstream projects.
+- Built foundational messaging infrastructure running WhatsApp core systems worldwide,
+ improving reliability and performance.
+
+
+#work(
+ company: "Toronto Metropolitan University",
+ title: "Graduate Research Assistant",
+ dates: dates-helper(from: "May 2024", to: "Sep 2024"),
+ location: "Toronto",
+)
+
+- Automated transformations for hundreds of media files,
+ using ffmpeg and unix primitives to parallelize workload.
+- Published frontend with AstroJS to generate only static HTML,
+ ensuring compatibility with many hosting providers.
+
+#work(
+ company: "Canadian Broadcasting Corporation",
+ title: "Network Engineering Intern",
+ dates: dates-helper(from: "May 2023", to: "Apr 2024"),
+ location: "Toronto",
+)
+
+- Designed custom PXE based provisioning for hundreds of devices on existing network,
+ eliminating manual configuration.
+- Architected hyper-converged Proxmox cluster for 2024 Olympics,
+ saving \$250k+ with reused hardware and open software.
+
+#work(
+ company: "WSP Canada",
+ title: "Student Engineer",
+ dates: dates-helper(from: "May", to: "Aug") + ", 2021, 2022",
+ location: "Toronto",
+)
+
+- Modernized subway control systems with modern C++,
+ replacing legacy code with newer STL functions.
+- Validated new electrical designs for power consumption,
+ cost efficiency, and viability with existing systems.
+
+== Projects
+
+*Custom Linux Distribution* ---
+Designed entire Linux distribution used for hosting production servers,
+daily desktop use, and embedded programming on a Raspberry Pi.
+Using NixOS, it supports enabling only required functionality at build time.
+Features systemd,
+an in-memory root filesystem,
+ZFS persistent storage with backups,
+and extremely hardened networking.
+
+*Distributed Compute Cluster* ---
+Bare-metal Kubernetes cluster managed with GitOps to be completely reproducible.
+Uses Cilium CNI for fast eBPF networking and BGP load balancing,
+FluxCD for cluster management,
+and CEPH distributed storage for stateful workloads.
+Air-gapped, automated cluster provisioning with custom PXEBOOT images.
+
+*ICER Compressor* ---
+Image compression library written in Rust,
+designed for deep-space communication.
+Hand tuned for speed and portability by using only integer arithmetic,
+no heap allocations, and no standard library by default.
+Achieves practically instant compressions, even on microprocessors.
+
+*Mirrorlist Generator* ---
+Fetches Arch Linux package mirrors,
+filtering them based on user parameters.
+Sorts and outputs formatted data compliant with the pacman package manager.
+Heavily outperforms default Python implementation.
+
+== Education
+
+#edu(
+ institution: "Toronto Metropolitan University",
+ dates: dates-helper(from: "Sep 2020", to: "Apr 2025"),
+ location: "Canada",
+ degree: "Bachelor's of Engineering, Computer Engineering",
+)
+
+- *Relevant Coursework*:
+ Data Structures, FPGA Programming, Compilers & Interpreters, Digital Systems, Computer Networks
+- *Extracurriculars*:
+ Member of student robotics design team,
+ teaching assistant for micro-processor courses.
+
+== Skills
+
+- *Languages*: #(
+ "Rust",
+ "Scheme",
+ "Nix",
+ "Haskell",
+ "Erlang",
+ "C++",
+ "Lua",
+ "TypeScript",
+ ).join(", ")
+
+- *Technologies*: #(
+ "Linux",
+ "Distributed Systems",
+ "Compilers",
+ "Virtualisation",
+ "Infrastructure Automation",
+ "Build Systems",
+ ).join(", ")
diff --git a/packages/cv/default.nix b/packages/cv/default.nix
new file mode 100644
index 0000000..9de6903
--- /dev/null
+++ b/packages/cv/default.nix
@@ -0,0 +1,5 @@
+{ pkgs, ... }:
+
+pkgs.runCommand "cv" { } ''
+ ${pkgs.typst}/bin/typst compile --format pdf ${./cv.typ} $out
+''
diff --git a/packages/perf-flamegraph.nix b/packages/perf-flamegraph.nix
new file mode 100644
index 0000000..b472b74
--- /dev/null
+++ b/packages/perf-flamegraph.nix
@@ -0,0 +1,12 @@
+# Script that collects perf timing for the execution of a command and writes a
+# flamegraph to stdout
+{ pkgs, ... }:
+
+pkgs.writeShellScriptBin "perf-flamegraph" ''
+ set -euo pipefail
+
+ ${pkgs.linuxPackages.perf}/bin/perf record -g --call-graph dwarf -F max "$@"
+ ${pkgs.linuxPackages.perf}/bin/perf script \
+ | ${pkgs.flamegraph}/bin/stackcollapse-perf.pl \
+ | ${pkgs.flamegraph}/bin/flamegraph.pl
+''
diff --git a/packages/resistors/default.nix b/packages/resistors/default.nix
new file mode 100644
index 0000000..6a038ea
--- /dev/null
+++ b/packages/resistors/default.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+pkgs.runCommandNoCC "resisto-rs" { } ''
+ mkdir -p $out
+ cp ${./index.html} $out/index.html
+ cp ${./styles.css} $out/styles.css
+''
diff --git a/packages/resistors/index.html b/packages/resistors/index.html
new file mode 100644
index 0000000..0683f49
--- /dev/null
+++ b/packages/resistors/index.html
@@ -0,0 +1,339 @@
+<!doctype html>
+<head>
+ <meta charset="utf-8" />
+ <title>rs-calc</title>
+ <link rel="stylesheet" href="styles.css" />
+ <meta name="viewport" content="initial-scale=1.0" />
+</head>
+
+<body>
+ <h1>rs-calc</h1>
+
+ <input id="resistor-query" placeholder="e.g. 3.3K" />
+ <label class="resistor-query-label" for="resistor-query">&#8486;</label>
+
+ <div id="resistor-result" style="display: none">
+ <h2>four bands</h2>
+ <div class="resistor-diagram resistor-diagram-4">
+ <div id="resistor-stripe-4-1" class="resistor-stripe"></div>
+ <div id="resistor-stripe-4-2" class="resistor-stripe"></div>
+ <div id="resistor-stripe-4-3" class="resistor-stripe"></div>
+ <div id="resistor-stripe-4-4" class="resistor-stripe">
+ &nbsp;<br />&nbsp;
+ </div>
+ <div
+ class="resistor-stripe-tolerance resistor-stripe"
+ style="color: #000; background-color: #cfb53b"
+ >
+ gold<br />&plusmn;5%
+ </div>
+ </div>
+ <h2>five bands</h2>
+ <div class="resistor-diagram resistor-diagram-5">
+ <div id="resistor-stripe-5-1" class="resistor-stripe"></div>
+ <div id="resistor-stripe-5-2" class="resistor-stripe"></div>
+ <div id="resistor-stripe-5-3" class="resistor-stripe"></div>
+ <div id="resistor-stripe-5-4" class="resistor-stripe"></div>
+ <div
+ class="resistor-stripe-tolerance resistor-stripe"
+ style="color: #fff; background-color: #964b00"
+ >
+ brown<br />&plusmn;1%
+ </div>
+ </div>
+ <h2>surface mount</h2>
+ <div id="resistor-smt-3" class="resistor-smt"></div>
+ </div>
+
+ <footer>
+ <p>
+ forked from
+ <a href="https://github.com/joewalnes/resisto.rs/" target="_blank">this</a
+ >, with removed analytics, cleaned up code.
+ <a href="https://github.com/kleidib" target="_blank">my github</a>
+ </p>
+ </footer>
+</body>
+
+<script>
+ // Resistor calculator.
+ // -Joe Walnes
+ // See resistors-test.html
+
+ var resistors = {};
+
+ // These hex codes came from
+ // http://en.wikipedia.org/wiki/Electronic_color_code
+ resistors.digitsToColors = {
+ "-2": { hex: "#c0c0c0", label: "#000", name: "silver", multiplier: "0.01" },
+ "-1": { hex: "#cfb53b", label: "#000", name: "gold", multiplier: "0.1" },
+ 0: { hex: "#000000", label: "#fff", name: "black", figure: "0" },
+ 1: {
+ hex: "#964b00",
+ label: "#fff",
+ name: "brown",
+ figure: "1",
+ multiplier: "10",
+ },
+ 2: {
+ hex: "#ff0000",
+ label: "#fff",
+ name: "red",
+ figure: "2",
+ multiplier: "100",
+ },
+ 3: {
+ hex: "#ffa500",
+ label: "#000",
+ name: "orange",
+ figure: "3",
+ multiplier: "1K",
+ },
+ 4: {
+ hex: "#ffff00",
+ label: "#000",
+ name: "yellow",
+ figure: "4",
+ multiplier: "10K",
+ },
+ 5: {
+ hex: "#9acd32",
+ label: "#000",
+ name: "green",
+ figure: "5",
+ multiplier: "100K",
+ },
+ 6: {
+ hex: "#6495ed",
+ label: "#000",
+ name: "blue",
+ figure: "6",
+ multiplier: "1M",
+ },
+ 7: {
+ hex: "#ee82ee",
+ label: "#000",
+ name: "purple",
+ figure: "7",
+ multiplier: "10M",
+ },
+ 8: {
+ hex: "#a0a0a0",
+ label: "#000",
+ name: "gray",
+ figure: "8",
+ multiplier: "100M",
+ },
+ 9: {
+ hex: "#ffffff",
+ label: "#000",
+ name: "white",
+ figure: "9",
+ multiplier: "1000M",
+ },
+ };
+
+ resistors.query = function (input) {
+ input = input
+ .replace(/ +?/g, "")
+ .replace(/ohm[s]?/, "")
+ .replace(/\u2126/, "")
+ .replace(/\.$/, "");
+
+ var value = this.parseValue(input);
+
+ if (value !== null) {
+ value = this.roundToSignificantPlaces(value, 3);
+
+ if ((value <= 99900000000 && value >= 1) || value === 0) {
+ var colors5 = this.numberTo5ColorDigits(value);
+ var colors4 = this.numberTo4ColorDigits(value);
+ var smt3 = value < 10 ? colors4[0].toString() : colors4.join("");
+ var self = this;
+
+ return {
+ value: value,
+ smt3: smt3,
+ //smt4: smt4,
+ //smtEia96: smtEia96,
+ formatted: this.formatValue(value),
+ colors5: colors5.map(function (d) {
+ return self.digitsToColors[d];
+ }),
+ colors4: colors4.map(function (d) {
+ return self.digitsToColors[d];
+ }),
+ };
+ }
+ }
+
+ return null;
+ };
+
+ /**
+ * Given ohm rating as string (e.g. 3.2m or 3M2), return
+ * integer value (e.g. 3200000).
+ */
+ resistors.parseValue = function (input) {
+ var multiplier = 1;
+ var match;
+
+ if ((match = input.match(/^(\d+)(\.(\d+))?([km])?$/i))) {
+ // e.g. 123, 1.23, 1M, 1.23M
+ var unit = match[4];
+ if (unit) {
+ if (unit == "k" || unit == "K") {
+ multiplier = 1000;
+ }
+ if (unit == "m" || unit == "M") {
+ multiplier = 1000000;
+ }
+ }
+
+ return (match[1] + "." + (match[3] || 0)) * multiplier;
+ } else if ((match = input.match(/^(\d+)([km])(\d+)$/i))) {
+ // e.g. 12K3
+ var unit = match[2];
+ if (unit) {
+ if (unit == "k" || unit == "K") {
+ multiplier = 1000;
+ }
+ if (unit == "m" || unit == "M") {
+ multiplier = 1000000;
+ }
+ }
+ return (match[1] + "." + (match[3] || 0)) * multiplier;
+ } else {
+ return null;
+ }
+ };
+
+ /**
+ * Round a value to significant places.
+ * e.g. (123456789, 3) -> 123000000)
+ * (0.0045678, 3) -> 0.00457)
+ */
+ resistors.roundToSignificantPlaces = function (value, significant) {
+ if (!value) {
+ return 0;
+ }
+ var nearest = Math.pow(
+ 10,
+ Math.floor(Math.log(Math.abs(value)) / Math.log(10)) - (significant - 1),
+ );
+ return Math.round(value / nearest) * nearest;
+ };
+
+ /**
+ * Given ohm rating as integer (e.g. 470000), return
+ * array of color digits (e.g. 4, 7, 0, 3). See digitsTo_Colors.
+ */
+ resistors.numberTo5ColorDigits = function (value) {
+ if (!value) {
+ return [0, 0, 0, 0]; // Special case
+ }
+
+ var precision = 5;
+ var digits = (
+ Math.floor(value * 100 * Math.pow(10, precision)) /
+ Math.pow(10, precision)
+ ).toString();
+ function getDigit(digits, i) {
+ var d = parseInt(digits[i]);
+ return isNaN(d) ? 0 : d;
+ }
+ return [
+ getDigit(digits, 0),
+ getDigit(digits, 1),
+ getDigit(digits, 2),
+ digits.length - 5,
+ ];
+ };
+
+ /**
+ * Given ohm rating as integer (e.g. 470000), return
+ * array of color digits (e.g. 4, 7, 0, 3). See digitsTo_Colors.
+ */
+ resistors.numberTo4ColorDigits = function (value) {
+ if (!value) {
+ return [0, 0, 0]; // Special case
+ }
+
+ var precision = 5;
+ var digits = (
+ Math.floor(value * 100 * Math.pow(10, precision)) /
+ Math.pow(10, precision)
+ ).toString();
+ function getDigit(digits, i) {
+ var d = parseInt(digits[i]);
+ return isNaN(d) ? 0 : d;
+ }
+ return [getDigit(digits, 0), getDigit(digits, 1), digits.length - 4];
+ };
+
+ /**
+ * Given a numeric value, format it like '3.2M' etc.
+ */
+ resistors.formatValue = function (value) {
+ if (value >= 1000000) {
+ return value / 1000000 + "M";
+ } else if (value >= 1000) {
+ return value / 1000 + "K";
+ } else {
+ return value.toString();
+ }
+ };
+</script>
+<script>
+ var queryEl = document.getElementById("resistor-query"),
+ resultEl = document.getElementById("resistor-result");
+
+ function showStripes(colors, prefix) {
+ for (var i = 0; i < colors.length; i++) {
+ var color = colors[i];
+ var stripeEl = document.getElementById(
+ "resistor-stripe-" + prefix + "-" + (i + 1),
+ );
+ stripeEl.style.backgroundColor = color.hex;
+ stripeEl.style.color = color.label;
+ var text = color.name + "<br>";
+
+ if (i == colors.length - 1) {
+ if (color.multiplier !== undefined) {
+ text += "&times;" + color.multiplier;
+ } else {
+ text += "&nbsp;";
+ }
+ } else {
+ text += color.figure;
+ }
+ stripeEl.innerHTML = text;
+ }
+ }
+
+ queryEl.onchange = queryEl.onkeyup = function () {
+ location.hash = encodeURIComponent(queryEl.value);
+ var result = resistors.query(queryEl.value);
+
+ if (result) {
+ resultEl.style.display = "block";
+ showStripes(result.colors4, "4");
+ showStripes(result.colors5, "5");
+ document.getElementById("resistor-smt-3").innerHTML = result.smt3;
+ } else {
+ resultEl.style.display = "none";
+ }
+ };
+
+ if (location.hash) {
+ queryEl.value = decodeURIComponent(location.hash.substring(1));
+ queryEl.onchange();
+ }
+
+ window.onhashchange = function () {
+ queryEl.value = decodeURIComponent(location.hash.substring(1));
+ queryEl.onchange();
+ };
+
+ queryEl.focus();
+</script>
diff --git a/packages/resistors/styles.css b/packages/resistors/styles.css
new file mode 100644
index 0000000..e2823e4
--- /dev/null
+++ b/packages/resistors/styles.css
@@ -0,0 +1,127 @@
+* {
+ /*font-family: 'firacode';*/
+ text-rendering: optimizeLegibility;
+}
+
+body {
+ margin: 10px 20px;
+ color: #cccccc;
+ background: #252525;
+ background-attachment: fixed !important;
+}
+
+h1 {
+ font-weight: normal;
+ font-size: 50px;
+ margin: -10px 0 10px 0;
+}
+
+h2 {
+ font-weight: normal;
+ font-size: 15px;
+}
+
+strong {
+ font-weight: normal;
+}
+
+footer {
+ position: absolute;
+ font-size: 12px;
+ color: #555;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 20px;
+}
+
+a {
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+footer a {
+ color: #775;
+}
+
+#resistor-query {
+ font-size: 20px;
+ border: 1px solid #999;
+ color: #fff;
+ background-color: #444;
+ outline: none;
+ width: 180px;
+}
+
+.resistor-stripe {
+ width: 80px;
+ height: 150px;
+ display: inline-block;
+ padding: 4px 4px;
+}
+
+.resistor-smt {
+ display: inline-block;
+ padding: 0px 10px;
+ font-size: 35px;
+ background-color: #000;
+ border-left: 15px solid silver;
+ border-right: 15px solid silver;
+ border-top: 1px solid silver;
+ border-bottom: 1px solid silver;
+ margin-right: 10px;
+}
+
+@media print, screen and (max-width: 520px) {
+ .resistor-stripe {
+ width: 55px;
+ font-size: 80%;
+ height: 100px;
+ }
+ .resistor-smt {
+ font-size: 25px;
+ border-left: 12px solid silver;
+ border-right: 12px solid silver;
+ }
+}
+
+@media print, screen and (max-width: 410px) {
+ .resistor-stripe {
+ width: 35px;
+ font-size: 55%;
+ height: 70px;
+ }
+ .resistor-smt {
+ font-size: 18px;
+ border-left: 8px solid silver;
+ border-right: 8px solid silver;
+ }
+}
+
+@media print, screen and (max-width: 300px) {
+ .resistor-stripe {
+ width: 25px;
+ font-size: 50%;
+ height: 60px;
+ padding: 2px;
+ }
+ .resistor-smt {
+ font-size: 13px;
+ border-left: 6px solid silver;
+ border-right: 6px solid silver;
+ padding: 2px 4px;
+ }
+}
+
+@media print, screen and (max-height: 660px) {
+ .resistor-stripe {
+ height: 55px;
+ }
+ footer {
+ position: inherit;
+ padding: 10px 0;
+ }
+}