aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKumar Damani <me@kumardamani.net>2026-07-24 04:29:08 +0000
committerKumar Damani <me@kumardamani.net>2026-07-24 04:29:08 +0000
commit3d615f7b4c19abf34f23ff09ea587a597f2de42a (patch)
tree10a0e4c3e0fa2c9b0254bb1826ae89f67fbc7df0
parent62fcd12a0a1ea557334dec1fa0cfdce69d003bd8 (diff)
converted to typst
-rw-r--r--.gitignore1
-rw-r--r--README.md36
-rwxr-xr-xbuild.sh13
-rw-r--r--ci.json1
-rw-r--r--contact.typ13
-rw-r--r--global/common.typ228
-rw-r--r--global/extlink.svg25
-rw-r--r--global/favicon.icobin0 -> 15086 bytes
-rw-r--r--global/favicon.svg8
-rw-r--r--global/main.css371
-rw-r--r--index.typ29
-rw-r--r--main.typ78
-rw-r--r--post/framework-server-p2.typ104
-rwxr-xr-xpost/framework-server-p2/current-lab.jpgbin0 -> 37401 bytes
-rwxr-xr-xpost/framework-server-p2/ethernet.pngbin0 -> 110306 bytes
-rwxr-xr-xpost/framework-server-p2/server-front.pngbin0 -> 87328 bytes
-rwxr-xr-xpost/framework-server-p2/server-inside.pngbin0 -> 221831 bytes
-rw-r--r--post/framework-server.typ558
-rwxr-xr-xpost/framework-server/bios.jpgbin0 -> 162196 bytes
-rwxr-xr-xpost/framework-server/bios2.pngbin0 -> 289613 bytes
-rwxr-xr-xpost/framework-server/case.jpgbin0 -> 111978 bytes
-rwxr-xr-xpost/framework-server/dmz.pngbin0 -> 69096 bytes
-rwxr-xr-xpost/framework-server/dns.jpgbin0 -> 59205 bytes
-rwxr-xr-xpost/framework-server/dri.pngbin0 -> 9650 bytes
-rwxr-xr-xpost/framework-server/error.jpgbin0 -> 43548 bytes
-rwxr-xr-xpost/framework-server/eth.jpgbin0 -> 107918 bytes
-rwxr-xr-xpost/framework-server/fail.pngbin0 -> 164105 bytes
-rwxr-xr-xpost/framework-server/feelsbadman.pngbin0 -> 32798 bytes
-rwxr-xr-xpost/framework-server/fix1.pngbin0 -> 23658 bytes
-rwxr-xr-xpost/framework-server/fix2.pngbin0 -> 67102 bytes
-rwxr-xr-xpost/framework-server/fix3.pngbin0 -> 27163 bytes
-rwxr-xr-xpost/framework-server/fw1.pngbin0 -> 29985 bytes
-rwxr-xr-xpost/framework-server/fw2.pngbin0 -> 15422 bytes
-rwxr-xr-xpost/framework-server/imp.pngbin0 -> 341529 bytes
-rwxr-xr-xpost/framework-server/lab.pngbin0 -> 85363 bytes
-rwxr-xr-xpost/framework-server/lan.pngbin0 -> 87550 bytes
-rwxr-xr-xpost/framework-server/mistakes.jpgbin0 -> 62973 bytes
-rwxr-xr-xpost/framework-server/nas.jpgbin0 -> 139364 bytes
-rwxr-xr-xpost/framework-server/net1.pngbin0 -> 22726 bytes
-rwxr-xr-xpost/framework-server/net2.pngbin0 -> 21835 bytes
-rwxr-xr-xpost/framework-server/network.pngbin0 -> 55633 bytes
-rwxr-xr-xpost/framework-server/pbr.pngbin0 -> 14982 bytes
-rwxr-xr-xpost/framework-server/pbr1.pngbin0 -> 33683 bytes
-rwxr-xr-xpost/framework-server/pbr2.pngbin0 -> 8422 bytes
-rwxr-xr-xpost/framework-server/pfnet.pngbin0 -> 9831 bytes
-rwxr-xr-xpost/framework-server/physical.pngbin0 -> 79028 bytes
-rwxr-xr-xpost/framework-server/port1.pngbin0 -> 6063 bytes
-rwxr-xr-xpost/framework-server/port2.pngbin0 -> 44834 bytes
-rwxr-xr-xpost/framework-server/rproxy.pngbin0 -> 48318 bytes
-rwxr-xr-xpost/framework-server/ups.jpgbin0 -> 151532 bytes
-rwxr-xr-xpost/framework-server/vps.pngbin0 -> 30087 bytes
-rwxr-xr-xpost/framework-server/wg.pngbin0 -> 23335 bytes
-rwxr-xr-xpost/framework-server/wrtnet.pngbin0 -> 57663 bytes
-rwxr-xr-xpost/framework-server/wrtnet2.pngbin0 -> 12214 bytes
-rwxr-xr-xpost/framework-server/wtf.jpgbin0 -> 41867 bytes
-rw-r--r--post/listing-my-fav-advice.typ87
-rwxr-xr-xpost/listing-my-fav-advice/balance.jpgbin0 -> 18818 bytes
-rwxr-xr-xpost/listing-my-fav-advice/boss.jpgbin0 -> 122736 bytes
-rwxr-xr-xpost/listing-my-fav-advice/chewing.gifbin0 -> 735263 bytes
-rwxr-xr-xpost/listing-my-fav-advice/indulgence.jpgbin0 -> 78009 bytes
-rwxr-xr-xpost/listing-my-fav-advice/money.jpgbin0 -> 21475 bytes
-rw-r--r--post/nas-upgrade.typ148
-rwxr-xr-xpost/nas-upgrade/cockpit-lxc.pngbin0 -> 72110 bytes
-rwxr-xr-xpost/nas-upgrade/ethernet.pngbin0 -> 110306 bytes
-rwxr-xr-xpost/nas-upgrade/nas-chassis.pngbin0 -> 72957 bytes
-rwxr-xr-xpost/nas-upgrade/oldnas.pngbin0 -> 89215 bytes
-rwxr-xr-xpost/nas-upgrade/zfs-pool.pngbin0 -> 79343 bytes
-rw-r--r--post/nixos-p1.typ459
-rwxr-xr-xpost/nixos-p1/boot.pngbin0 -> 12893 bytes
-rwxr-xr-xpost/nixos-p1/dwl.pngbin0 -> 3082421 bytes
-rwxr-xr-xpost/nixos-p1/fox.jpgbin0 -> 30886 bytes
-rwxr-xr-xpost/nixos-p1/rice.pngbin0 -> 1670023 bytes
-rw-r--r--post/programming-as-art.typ98
-rwxr-xr-xpost/programming-as-art/code.pngbin0 -> 530926 bytes
-rwxr-xr-xpost/programming-as-art/math.pngbin0 -> 398307 bytes
-rwxr-xr-xpost/programming-as-art/teaching.pngbin0 -> 410632 bytes
-rwxr-xr-xpost/programming-as-art/wand.pngbin0 -> 356173 bytes
-rw-r--r--post/programs.typ103
-rwxr-xr-xpost/programs/dwl.pngbin0 -> 3082421 bytes
-rw-r--r--post/self-host.typ138
-rwxr-xr-xpost/self-host/6imv05.jpgbin0 -> 66020 bytes
-rwxr-xr-xpost/self-host/elections.jpgbin0 -> 97041 bytes
-rwxr-xr-xpost/self-host/freedom.jpgbin0 -> 300014 bytes
-rwxr-xr-xpost/self-host/office.pngbin0 -> 1443597 bytes
-rwxr-xr-xpost/self-host/police.jpgbin0 -> 190074 bytes
-rwxr-xr-xpost/self-host/why.jpgbin0 -> 110502 bytes
-rw-r--r--site.typ68
-rw-r--r--software.typ69
88 files changed, 2612 insertions, 23 deletions
diff --git a/.gitignore b/.gitignore
index 92b2793..b895d04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
.direnv
+build/
diff --git a/README.md b/README.md
index 3753237..8deab58 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,20 @@
-# Install
-```bash
-git submodule init
-git submodule update --remote
-```
+My website/blog repo with Typst using html, and bundle experimental features.
-# Develop
-```bash
+```sh
nix develop
-hugo new content/post/<post-slug>/index.md
-```
-# Run (dev server)
-```bash
-nix develop
-hugo server -D
+# dev server for live updates
+typst watch --root . --features bundle,html -f bundle main.typ build
-# OR for docker:
-docker run --rm -it \
- -v $(pwd):/src \
- -p 1313:1313 \
- klakegg/hugo:latest \
- server -D
+# compile
+./build.sh
```
-Then go to `localhost:1313`.
-# Publish
-```bash
-./deploy.sh
+Gitlab CI will automatically inject a `ci.json` file:
+```json
+{
+ "date": "some date",
+ "link": "https://gitlab.com/.../job/42"
+}
```
+used by the `compile` step for the final output.
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..7fb4bed
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+# Build kumardamani.net into ./build using Typst's bundle export.
+#
+# Usage: ./build.sh [output-dir] (default output dir: build)
+set -e
+
+cd "$(dirname "$0")"
+out=${1:-build}
+
+rm -rf "$out"
+typst compile --root . --features bundle,html -f bundle main.typ "$out"
+
+echo "Built site in $out/"
diff --git a/ci.json b/ci.json
new file mode 100644
index 0000000..d361606
--- /dev/null
+++ b/ci.json
@@ -0,0 +1 @@
+{ "date": "July 24, 2026", "link": "https://gitlab.com/kumardamani.net" }
diff --git a/contact.typ b/contact.typ
new file mode 100644
index 0000000..2896f3b
--- /dev/null
+++ b/contact.typ
@@ -0,0 +1,13 @@
+#import "/global/common.typ": *
+
+#let doc = [
+ Want to contact me? You can use one of these methods:
+
+ #table(
+ columns: (auto, auto),
+ [E-Mail], link("mailto:me@kumardamani.net")[me\@kumardamani.net],
+ [GitLab], extlink("https://gitlab.com/kdam0/")[kdam0],
+ [Resume], html.a(href: "KumarDamaniCV.pdf")[KumarDamaniCV.pdf],
+ [Photos], extlink("https://nc.kumardamani.net/apps/memories/a/vSk9IAWVUx2vcqphL9nMnLFmDQBDZWPr")[Nextcloud Memories],
+ )
+]
diff --git a/global/common.typ b/global/common.typ
new file mode 100644
index 0000000..14738a6
--- /dev/null
+++ b/global/common.typ
@@ -0,0 +1,228 @@
+// Site template for kumardamani.net, adapted from the example Typst blog.
+// Structured for Typst's bundle export: pages are emitted as #document
+// elements from the main.typ entrypoint and cross-page links use labels.
+
+#let _site-info = state("site-info", none)
+#let _page-name = state("page-name", none)
+#let _page-dir = state("page-dir", "")
+
+// Image paths collected from pages, emitted as bundle assets in main.typ.
+#let _asset-paths = state("asset-paths", ())
+
+#let srcurl = "https://gitlab.com/kdam0/kumardamani.net"
+
+// Deployment info, overwritten by CI per build:
+// printf '{ "date": "%s", "link": "%s" }\n' \
+// "$(date '+%B %d, %Y' | sed 's/ 0/ /')" "$CI_JOB_URL" > ci.json
+// date is pre-formatted for display, e.g. "July 24, 2026".
+#let ci = json("/ci.json")
+
+// Page ids ("post/self-host") map to labels on the corresponding #document
+// elements ("post-self-host"); typst resolves relative links between
+// documents automatically.
+#let label-for(id) = label(id.replace("/", "-"))
+
+#let extlink(
+ dest,
+ attrs: (:),
+ body
+) = html.elem(
+ "a",
+ attrs: (
+ class: "extlink",
+ rel: "external",
+ target: "_blank",
+ href: dest,
+ ) + attrs,
+ body
+)
+
+#let navbar(pages, current) = {
+ html.elem("nav", html.elem("ul", {
+ for (pagename, prefs) in pages {
+ let (title,) = prefs
+ let hidden = prefs.at("hidden", default: false)
+
+ if hidden and pagename != current {
+ continue
+ }
+
+ let attrs = (:)
+ let class = ()
+
+ if pagename == current {
+ class += ("current",)
+ }
+
+ if hidden {
+ class += ("hidden",)
+ }
+
+ if class != () {
+ attrs += ("class": class.join(" "))
+ }
+
+ if pagename == current {
+ // the current page's nav item is not a link
+ html.elem("li", attrs: attrs, title)
+ } else if "url" in prefs {
+ html.elem("li", attrs: attrs, extlink(prefs.url, title))
+ } else {
+ html.elem("li", attrs: attrs, link(label-for(pagename), title))
+ }
+ }
+ }))
+}
+
+// Image helpers. These emit plain <img> tags with literal src paths so that
+// images stay as separate files instead of being embedded into the HTML, and
+// register local sources as bundle assets (emitted in main.typ).
+#let img(src, alt: "", width: none) = {
+ if not src.starts-with("http") {
+ context {
+ let dir = _page-dir.get()
+ _asset-paths.update(s => s + (dir + src,))
+ }
+ }
+
+ let attrs = (src: src, alt: alt)
+ if width != none {
+ attrs.insert("style", "width: " + width)
+ }
+ html.elem("img", attrs: attrs)
+}
+
+#let fig(src, alt: "", caption: none, width: none) = html.elem("figure", {
+ img(src, alt: alt, width: width)
+ if caption != none {
+ html.elem("figcaption", caption)
+ }
+})
+
+#let _preferred-title(id) = {
+ let page = _site-info.get().at("pages").at(id)
+
+ if "long-title" in page {
+ return page.at("long-title")
+ } else {
+ return page.at("title")
+ }
+}
+
+// The preferred (long, if available) title of the current page.
+#let preferred-title = context {
+ _preferred-title(_page-name.get())
+}
+
+// The publication date of the current page, if any.
+#let page-date = context {
+ let info = _site-info.get().at("pages").at(_page-name.get())
+ if "date" in info {
+ html.elem("p", attrs: (class: "date"),
+ html.elem("time", attrs: (datetime: info.date), info.date))
+ }
+}
+
+// Link to another page on the site; relative paths are resolved by typst.
+#let pagelink(id, body) = link(label-for(id), body)
+
+// Link to another page, using its preferred title as the link text.
+#let pageref(id) = context {
+ link(label-for(id), _preferred-title(id))
+}
+
+#let page(
+ pagename,
+ site-info,
+ banner: none,
+ stylesheets: (),
+ body
+) = {
+ let site = site-info.at("title", default: "Untitled")
+ let pages = site-info.at("pages", default: (:))
+
+ if banner == none {
+ banner = html.elem("h1", attrs: (class: "banner"), site)
+ }
+
+ // Prefix for links back to the site root from nested pages (posts).
+ let parts = pagename.split("/")
+ let depth = parts.len() - 1
+ let base = "../" * depth
+
+ html.html(
+ lang: "en",
+ {
+ html.head({
+ html.meta(charset: "utf-8")
+ html.meta(name: "viewport", content: "width=device-width, initial-scale=1")
+ html.title(pages.at(pagename).title + " – " + site)
+
+ html.elem("link", attrs: ("rel": "vcs-git", "title": "site source", "href": srcurl))
+ html.link(rel: "icon", href: base + "favicon.svg", type: "image/svg+xml")
+ html.link(rel: "icon", href: base + "favicon.ico")
+ html.elem("link", attrs: (rel: "preload", href: base + "extlink.svg", "as": "image"))
+ html.link(rel: "stylesheet", href: base + "main.css")
+
+ for ss in stylesheets {
+ html.link(rel: "stylesheet", href: base + ss)
+ }
+ })
+ html.body({
+ _site-info.update(site-info)
+ _page-name.update(pagename)
+ _page-dir.update(if depth > 0 { parts.slice(0, -1).join("/") + "/" } else { "" })
+
+ show html.elem: it => {
+ if "title" in it.attrs {
+ let attrs = it.attrs
+ let has-class = "class" in it.attrs
+
+ if not has-class or regex("\btooltip\b") not in it.attrs.class {
+ if has-class {
+ attrs.class += " tooltip"
+ } else {
+ attrs.class = "tooltip"
+ }
+
+ it = html.elem(it.tag, attrs: attrs, it.body)
+ }
+ }
+
+ it
+ }
+
+ show outline.entry: it => {
+ if it.prefix() != none {
+ html.span(class: "prefix", it.prefix())
+ }
+ link(it.element.location(), it.body())
+ }
+
+ html.header({
+ banner
+ navbar(pages, pagename)
+ })
+
+ html.main(body)
+ html.hr()
+ html.footer({
+ html.span(class: "updated")[Updated: #ci.date.]
+ html.span(class: "logs")[#extlink(ci.link)[Logs] for nerds!]
+ })
+ })
+ }
+ )
+}
+
+// Scaffold for post pages: numbered headings (the heading counter is global
+// to the bundle, so it is reset per document) with title and date on top.
+#let post-page(pagename, site-info, body) = page(pagename, site-info, {
+ set heading(numbering: "1.")
+ counter(heading).update(0)
+
+ title(preferred-title)
+ page-date
+
+ body
+})
diff --git a/global/extlink.svg b/global/extlink.svg
new file mode 100644
index 0000000..3673c92
--- /dev/null
+++ b/global/extlink.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="24"
+ height="24"
+ viewBox="0 0 24 24"
+ version="1.1"
+ id="svg1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <defs
+ id="defs1" />
+ <g
+ id="layer1">
+ <path
+ style="fill:#000000;stroke:none;stroke-width:10"
+ d="M 12,0 H 24 V 12 H 21 L 21.020819,5.1005019 11.121321,15 9,15 V 12.878679 L 18.899498,2.9791809 12,3 Z"
+ id="path1" />
+ <path
+ style="fill:#000000;stroke:none;stroke-width:10"
+ d="M 0,6 V 24 H 18 V 12 h -3 v 9 H 3 V 9 h 9 V 6 Z"
+ id="path2" />
+ </g>
+</svg>
diff --git a/global/favicon.ico b/global/favicon.ico
new file mode 100644
index 0000000..49cf463
--- /dev/null
+++ b/global/favicon.ico
Binary files differ
diff --git a/global/favicon.svg b/global/favicon.svg
new file mode 100644
index 0000000..b8d5205
--- /dev/null
+++ b/global/favicon.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
+ <!-- crown -->
+ <path d="M 22,10 L 42,10 L 47,38 L 17,38 Z" fill="#3a3f44"/>
+ <!-- band -->
+ <path d="M 17.6,31 L 46.4,31 L 47,38 L 17,38 Z" fill="#22262a"/>
+ <!-- brim: downward sloping skirt -->
+ <path d="M 17,36 L 47,36 L 60,48 Q 61.5,51.5 57,52 L 7,52 Q 2.5,51.5 4,48 Z" fill="#5c646b"/>
+</svg>
diff --git a/global/main.css b/global/main.css
new file mode 100644
index 0000000..42951f4
--- /dev/null
+++ b/global/main.css
@@ -0,0 +1,371 @@
+/* SPDX-License-Identifier: MIT */
+/* SPDX-FileCopyrightText: 2024-2026 Katalin Rebhan <me@dblsaiko.net> */
+/* Adapted for kumardamani.net */
+
+:root {
+ --button-border: color-mix(in oklab, ButtonFace, CanvasText 10%);
+ --extlink-icon: url('extlink.svg');
+
+ /* base font size is 16px, which is 12pt */
+ --font-size-px-mul: calc(1rem / 16);
+ --font-size-pt-mul: calc(1rem / 12);
+
+ /* fonts we use */
+ --shell-ff: system-ui, ui-sans-serif, sans-serif;
+ --shell-fs: 1rem;
+ --shell-font: var(--shell-fs) var(--shell-ff);
+
+ --body-ff: var(--shell-ff);
+ --body-fs: 1rem;
+ --body-font: var(--body-fs) var(--body-ff);
+
+ --mono-ff: ui-monospace, monospace;
+ --mono-fs: calc(13 * var(--font-size-px-mul)); /* according to Safari */
+ --mono-font: var(--mono-fs) var(--mono-ff);
+
+ /* some extra lengths */
+ --elem-spacing: 0.5rem;
+ --navbar-extra: 0.15rem;
+ --border-size: 0.15rem;
+ --text-skip: 0.2em;
+}
+
+@media not (pointer: coarse) {
+ :root {
+ /* on displays with a non-coarse pointer (i.e. desktop computers),
+ shrink the shell font a bit because we can get away with it */
+ --shell-fs: calc(10 * var(--font-size-pt-mul));
+ }
+}
+
+html {
+ max-width: 800px;
+ height: min(100%, 600px);
+ /* importantly, we never set font-size on the html element, so we can use
+ rem later to refer to the browser's default which might be modified by
+ the user. */
+ font-family: var(--body-ff);
+ display: flex;
+ flex-direction: column;
+ color-scheme: light dark;
+ -moz-text-size-adjust: none;
+ -webkit-text-size-adjust: none;
+ text-size-adjust: none;
+ background: Canvas;
+}
+
+body {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+pre, xmp, plaintext, listing, tt, code, kbd, samp {
+ font: var(--mono-font);
+}
+
+header {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ align-items: baseline;
+ margin: 0 0 var(--elem-spacing) 0;
+ font: var(--shell-font);
+}
+
+header > nav {
+ display: flex;
+ flex-wrap: nowrap;
+ text-wrap: nowrap;
+ flex-grow: 1;
+ overflow-x: auto;
+ padding: 0 calc(var(--elem-spacing) + .1rem);
+ border-bottom: var(--border-size) solid var(--button-border);
+}
+
+header > nav > ul {
+ display: contents;
+}
+
+header > nav a:link {
+ text-decoration: unset;
+ color: unset;
+}
+
+header > nav a:visited {
+ color: unset;
+}
+
+header > nav li {
+ display: inline;
+ padding: 0 calc(var(--elem-spacing) + var(--navbar-extra));
+ margin: 0 calc(-1 * var(--navbar-extra));
+}
+
+header > nav li.current {
+ background-color: ButtonFace;
+ color: ButtonText;
+}
+
+header > nav li.hidden {
+ font-style: italic;
+}
+
+.banner {
+ font-size: 1.5em;
+ font-weight: unset;
+ margin: 0 var(--elem-spacing) 0 0;
+}
+
+main {
+ flex-grow: 1;
+ text-wrap-style: pretty;
+}
+
+main > :first-child {
+ margin-top: 0;
+}
+
+main > :last-child {
+ margin-bottom: 0;
+}
+
+hr {
+ /* Fixes <hr> collapsing to width 0 inside of a flex container */
+ margin-left: 0;
+ margin-right: 0;
+ align-self: stretch;
+
+ /* Same style as navbar */
+ border: 0;
+ border-bottom: var(--border-size) solid var(--button-border);
+}
+
+pre {
+ overflow: auto;
+}
+
+blockquote {
+ display: block;
+ margin-inline-start: var(--elem-spacing);
+ margin-inline-end: 0;
+ border-inline-start: var(--border-size) solid;
+ padding-inline-start: var(--text-skip);
+}
+
+/* Safari does not like font-size: 1rem + clip-path mixed units (FB22092644).
+ Hope we never have to combine both! */
+.biglist > li .description > a.extlink::after {
+ font-size: unset;
+}
+
+a.extlink::after {
+ padding-right: 0.5em;
+ color: CanvasText;
+ background-color: currentcolor;
+ mask: var(--extlink-icon);
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ clip-path: polygon(
+ calc(50% - 0.25em) calc(50% - 0.25em),
+ calc(50% + 0.25em) calc(50% - 0.25em),
+ calc(50% + 0.25em) calc(50% + 0.25em),
+ calc(50% - 0.25em) calc(50% + 0.25em)
+ );
+ content: "";
+ vertical-align: super;
+ line-height: 0;
+ font-size: 1rem;
+}
+
+.biglist {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 1em;
+ padding: 0;
+}
+
+.biglist > li {
+ display: block;
+}
+
+.biglist > li > h2 {
+ float: left;
+ margin: 0;
+ font-size: unset;
+}
+
+.biglist > li > h2::after {
+ color: GrayText;
+ content: "—";
+ font-weight: initial;
+ margin: 0 var(--text-skip);
+}
+
+.biglist > li .description {
+ display: inline;
+ margin: 0;
+}
+
+.biglist > li .source {
+ float: right;
+ margin: 0;
+}
+
+.biglist > li .source::before {
+ color: GrayText;
+ content: "—";
+ margin: 0 var(--text-skip);
+}
+
+article {
+ hyphens: auto;
+}
+
+article > p, article > blockquote {
+ text-align: justify;
+}
+
+article > :first-child {
+ margin-top: 0;
+}
+
+article > :last-child {
+ margin-bottom: 0;
+}
+
+article > nav li {
+ display: contents;
+}
+
+article > nav ol {
+ display: grid;
+ column-gap: var(--text-skip);
+ grid-template-columns: 0 1fr;
+}
+
+article > nav > ol {
+ overflow: auto;
+}
+
+article > nav ol {
+ grid-column: 2;
+}
+
+article > nav .prefix {
+ user-select: none;
+ grid-column: 1;
+ justify-self: end;
+}
+
+article > nav a {
+ grid-column: 2;
+ justify-self: start;
+}
+
+footer {
+ display: grid;
+ /* equal flexible side tracks keep the middle track truly centered */
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+ font: var(--shell-font);
+}
+
+footer > .updated {
+ grid-column: 2;
+ text-align: center;
+}
+
+footer > .logs {
+ grid-column: 3;
+ justify-self: end;
+}
+
+footer > ul {
+ display: contents;
+}
+
+footer > ul > li {
+ display: inline;
+ font: var(--shell-font);
+}
+
+footer > ul > li:not(:last-child)::after {
+ color: GrayText;
+ content: "—";
+ font-weight: initial;
+ margin: 0 var(--text-skip);
+ padding: 0;
+}
+
+.motb {
+ font-style: italic;
+ color: GrayText;
+ white-space: pre-wrap;
+}
+
+.tooltip {
+ text-decoration: underline;
+ text-decoration-style: dotted;
+ cursor: help;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+th, td {
+ padding: 0;
+ border: 0 solid transparent;
+ background-clip: padding-box;
+}
+
+tr > td + td {
+ border-left-width: 1.5em;
+}
+
+tr + tr > td {
+ border-top-width: 0.2em;
+}
+
+/* --- additions for kumardamani.net --- */
+
+/* publication date under post titles */
+.date {
+ color: GrayText;
+ font: var(--shell-font);
+ margin-top: 0;
+}
+
+figure {
+ margin: 1em 0;
+ text-align: center;
+}
+
+figcaption {
+ color: GrayText;
+ margin-top: 0.3em;
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+}
+
+p > img:only-child {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+pre {
+ padding: 0.5em;
+ border: 1px solid var(--button-border);
+}
+
+details {
+ margin: 1em 0;
+}
+
+summary {
+ cursor: pointer;
+}
diff --git a/index.typ b/index.typ
new file mode 100644
index 0000000..b679e24
--- /dev/null
+++ b/index.typ
@@ -0,0 +1,29 @@
+#import "/global/common.typ": *
+
+#let doc = [
+ Hi. My name is Kumar. Some people call me _kd_, or _ku-band-mar_, I'm known
+ for being...
+
+ - an advocate for de-centralization and self-hosting. #pagelink("post/self-host")[Why?]
+ - a programming purist, and a linguistics enthusiast.
+ - a recent amateur photographer. See some of my
+ #extlink("https://nc.kumardamani.net/apps/memories/a/vSk9IAWVUx2vcqphL9nMnLFmDQBDZWPr")[pics].
+ - against social-media. I think they lead to dystopia.
+ #link(<privacy>)[Read more]
+ - an NLP specialist from UofT.
+
+ Some of my software is linked on the #pageref("software") page along with a
+ list of all my public repositories. My active projects are my Nix dotfiles,
+ my home-lab infrastructure repository, and a Namecheap DNS CLI.
+
+ Posts I have written, newest first:
+
+ - #pageref("post/framework-server-p2")
+ - #pageref("post/nas-upgrade")
+ - #pageref("post/listing-my-fav-advice")
+ - #pageref("post/framework-server")
+ - #pageref("post/nixos-p1")
+ - #pageref("post/programming-as-art")
+ - #pageref("post/self-host")
+ - #pageref("post/programs")
+]
diff --git a/main.typ b/main.typ
new file mode 100644
index 0000000..1ed62ff
--- /dev/null
+++ b/main.typ
@@ -0,0 +1,78 @@
+// Entrypoint for the kumardamani.net bundle. Compiling this file with
+// typst compile --root . --features bundle,html -f bundle main.typ <outdir>
+// emits the whole site: one HTML document per page plus all assets.
+
+#import "/global/common.typ": *
+#import "/site.typ": site-info
+
+#import "/index.typ" as index-page
+#import "/software.typ" as software-page
+#import "/contact.typ" as contact-page
+
+#import "/post/framework-server-p2.typ" as post-framework-server-p2
+#import "/post/nas-upgrade.typ" as post-nas-upgrade
+#import "/post/listing-my-fav-advice.typ" as post-listing-my-fav-advice
+#import "/post/framework-server.typ" as post-framework-server
+#import "/post/nixos-p1.typ" as post-nixos-p1
+#import "/post/programming-as-art.typ" as post-programming-as-art
+#import "/post/self-host.typ" as post-self-host
+#import "/post/programs.typ" as post-programs
+
+#let title-for(id) = site-info.pages.at(id).title + " – " + site-info.title
+
+#document("index.html", title: title-for("index"))[
+ #page("index", site-info, index-page.doc)
+] <index>
+
+#document("software.html", title: title-for("software"))[
+ #page("software", site-info, software-page.doc)
+] <software>
+
+#document("contact.html", title: title-for("contact"))[
+ #page("contact", site-info, contact-page.doc)
+] <contact>
+
+#document("post/framework-server-p2.html", title: title-for("post/framework-server-p2"))[
+ #post-page("post/framework-server-p2", site-info, post-framework-server-p2.doc)
+] <post-framework-server-p2>
+
+#document("post/nas-upgrade.html", title: title-for("post/nas-upgrade"))[
+ #post-page("post/nas-upgrade", site-info, post-nas-upgrade.doc)
+] <post-nas-upgrade>
+
+#document("post/listing-my-fav-advice.html", title: title-for("post/listing-my-fav-advice"))[
+ #post-page("post/listing-my-fav-advice", site-info, post-listing-my-fav-advice.doc)
+] <post-listing-my-fav-advice>
+
+#document("post/framework-server.html", title: title-for("post/framework-server"))[
+ #post-page("post/framework-server", site-info, post-framework-server.doc)
+] <post-framework-server>
+
+#document("post/nixos-p1.html", title: title-for("post/nixos-p1"))[
+ #post-page("post/nixos-p1", site-info, post-nixos-p1.doc)
+] <post-nixos-p1>
+
+#document("post/programming-as-art.html", title: title-for("post/programming-as-art"))[
+ #post-page("post/programming-as-art", site-info, post-programming-as-art.doc)
+] <post-programming-as-art>
+
+#document("post/self-host.html", title: title-for("post/self-host"))[
+ #post-page("post/self-host", site-info, post-self-host.doc)
+] <post-self-host>
+
+#document("post/programs.html", title: title-for("post/programs"))[
+ #post-page("post/programs", site-info, post-programs.doc)
+] <post-programs>
+
+// global assets
+#asset("main.css", read("/global/main.css", encoding: none))
+#asset("extlink.svg", read("/global/extlink.svg", encoding: none))
+#asset("favicon.ico", read("/global/favicon.ico", encoding: none))
+#asset("favicon.svg", read("/global/favicon.svg", encoding: none))
+
+// image assets collected from pages via the img/fig helpers
+#context {
+ for path in _asset-paths.final().dedup() {
+ asset(path, read("/" + path, encoding: none))
+ }
+}
diff --git a/post/framework-server-p2.typ b/post/framework-server-p2.typ
new file mode 100644
index 0000000..e40b8e7
--- /dev/null
+++ b/post/framework-server-p2.typ
@@ -0,0 +1,104 @@
+#import "/global/common.typ": *
+
+#let doc = [
+My process of converting my Framework server back into a laptop.
+
+My wife needs an upgrade from her 2015 MacBook Pro, but shelling out a cool
+\$1600 + tax for one of the shiny new _M_ MacBooks for the 16GB variant hardly
+seems an ideal proposition now that Apple has relinquished consideration of its
+customers who would prefer to amortize upgrades over the lifespan of a machine.
+It seems that leaving the Mac platform is our only consumer-friendly option,
+which avoids fighting a losing battle with Apple w.r.t. upgradability.
+
+#quote(block: true)[Framework has entered the chat.]
+
+Meanwhile, Framework seems to have upheld its promise of providing a path to
+long-term upgradability for its products. The new Ryzen mainboards being
+compatible with my original Framework 13 (2021) is an example of this. So at
+this point, I'm far more inclined to commit to the Framework ecosystem for my
+wife (and probably myself) than Apple.
+
+Other than the initial non-trivial pain of migrating her from macOS to Windows,
+she should have a substantially better user-experience running all of the Adobe
+crapware she wants on my old Framework.
+
+*The only problem is that my entire home-lab currently
+#pagelink("post/framework-server")[runs on my Framework].*
+
+Time to go shopping!
+
+= The Pledge
+
+We'll be using second-hand equipment as much as possible. After all, the point
+of all this is to avoid spending \$1600 on a new laptop. So we should be well
+under that to make it worthwhile.
+
+I picked these up from eBay:
+
+#fig("framework-server-p2/server-front.png", alt: "server front")
+#fig("framework-server-p2/ethernet.png", alt: "ethernet adapter")
+
+#table(
+ columns: 2,
+ [Part], [Price (shipped)],
+ [HP Z240 Workstation Intel Xeon 3.4GHz 16G RAM 180G SSD Nvidia K620], [\$150],
+ [Mellanox ConnectX-2 PCIe x8 10Gbe SFP+ network card], [\$23],
+)
+
+#quote(block: true)[
+ My Framework currently has 32GB of memory, and while this is much less, it
+ will still be okay with my current utilization for my current workload.
+]
+
+One nice thing about this case is that it would be possible to rack-mount this
+workstation sideways on a standard rack shelf if I decided to go that route in
+the future.
+
+Also, I don't presently have 10Gb networking equipment at home to plug anything
+into that network card, but I'm getting it now for future-proofing.
+
+IPMI would have been nice-to-have, but I decided that for this price, and the
+bonus of that Nvidia K620 was too good to pass up! This is an immediate upgrade
+from my current integrated Iris GPU on my Framework server.
+
+#fig("framework-server-p2/server-inside.png", alt: "server inside")
+
+All of my home-lab VMs, and LXC containers run within Proxmox. So I set up a
+fresh Proxmox here.
+
+Fortunately for me, `lspci` showed all the hardware on the first go. But to use
+(pass-through) the Nvidia GPU in my guests, I need to first set up the Proxmox
+host. See my git repo for what I did to make this work.
+
+= The Turn
+
+To make the migration of our services _less painful_, we utilize Proxmox's
+migration features. We just need to make sure our nodes are part of a cluster.
+
+*Create the cluster on the old node.* Then, from the new node, we *join* the
+existing cluster.
+
+#quote(block: true)[
+ Proxmox 8 will not let a node with existing guests join a cluster.
+]
+
+= The Prestige
+
+Now it really is as easy as selecting a VM or an LXC container, hitting the
+Migrate button, and watching your guest fly away to the new node! For guests
+with large OS disks, it takes a little longer to copy over since my network is
+slow. Eventually, I will use Ceph-backed VM storage to make this a
+zero-downtime op.
+
+In a few minutes, I could knock out all of my services. Refer to my gitlab
+project to see how I handled the Nvidia K60 pass-through to my Nextcloud AIO
+instance and Plex guests.
+
+Here's what my "rack" looks like now:
+
+#fig("framework-server-p2/current-lab.jpg", alt: "current lab")
+
+Once everything was back online, I powered down the old Framework server and
+re-installed it back onto its laptop chassis, bought a W10 license, and
+finished the rest of the installation.
+]
diff --git a/post/framework-server-p2/current-lab.jpg b/post/framework-server-p2/current-lab.jpg
new file mode 100755
index 0000000..d64be2e
--- /dev/null
+++ b/post/framework-server-p2/current-lab.jpg
Binary files differ
diff --git a/post/framework-server-p2/ethernet.png b/post/framework-server-p2/ethernet.png
new file mode 100755
index 0000000..f39110d
--- /dev/null
+++ b/post/framework-server-p2/ethernet.png
Binary files differ
diff --git a/post/framework-server-p2/server-front.png b/post/framework-server-p2/server-front.png
new file mode 100755
index 0000000..31e34da
--- /dev/null
+++ b/post/framework-server-p2/server-front.png
Binary files differ
diff --git a/post/framework-server-p2/server-inside.png b/post/framework-server-p2/server-inside.png
new file mode 100755
index 0000000..79e7139
--- /dev/null
+++ b/post/framework-server-p2/server-inside.png
Binary files differ
diff --git a/post/framework-server.typ b/post/framework-server.typ
new file mode 100644
index 0000000..4e9d5b6
--- /dev/null
+++ b/post/framework-server.typ
@@ -0,0 +1,558 @@
+#import "/global/common.typ": *
+
+#let doc = [
+How I re-purposed my Framework as a standalone server for my home-lab.
+
+#quote(block: true)[
+ This is not sponsored by Framework ... though I wish it were :)
+]
+
+= Primary Motivation
+
+I \#feelsbad for abusing my lowly NAS -
+
+#img("framework-server/feelsbadman.png", alt: "feelsbadman", width: "150px")
+
+Its not it's fault that its owner expected it to run Collabora Online while
+running all his other services without instantly crashing...
+
+#fig("framework-server/error.jpg", alt: "meme of error page")
+
+Did I learn anything? Basically a NAS should just do NAS things like worry
+about storage. Compute should be handled by something else.
+
+#quote(block: true)[Lesson learned.]
+
+= Secondary Motivation
+
+Battery life is an issue with this gen.
+
+For my personal laptop I'd want something that has "all day" battery like the
+newer gen Intels or Ryzens.
+
+My plan is to snag one of those when prices become more attractive and retrofit
+it into my now hollow laptop chassis.
+
+Isn't this the Framework
+#extlink("https://frame.work/ca/en/about#what-we-re-fixing")[mission]?
+
+#quote(block: true)[
+ "Even better, what we've done to enable repair also opens up upgradability
+ and customization. This lets you get exactly the product you need and
+ extends usable lifetime too."
+]
+
+= Idea
+
+Re-purpose my 99%-of-the-time idle personal Framework laptop which is already
+overkill for the remaining 1%-of-the-time tasks, to be a hypervisor server.
+
+Setup my home-lab services there, and provide data storage for those services
+over NFS from my NAS.
+
+I'll setup a dedicated "workstation" VM for those 1%-of-the-time tasks.
+
+#fig("framework-server/physical.png", alt: "physical architecture image")
+
+= Prior to starting!
+
+To avoid making the same mistakes I made, do the following *while the mainboard
+is still in the laptop chassis!*
+
+#fig("framework-server/mistakes.jpg", alt: "mistakes meme")
+
++ *Update your BIOS firmware to at least 3.17.* It is *impossible* to do so
+ once the battery is un-plugged from the board on older firmwares. I learned
+ this the hard-way. This update will provide the critical options below for
+ this process to be feasible.
++ In your BIOS settings, enable options for optimizing CPU performance over
+ battery life.
++ In your BIOS settings, enable "Standalone operation".
++ In your BIOS settings, under "Boot" enable the option to power-on on
+ power-attach.
+ #fig("framework-server/bios.jpg", alt: "bios image")
++ Install Proxmox VE. Since this will require a screen, and keyboard input, you
+ might as well do it now. See the #link(<hypervisor>)[hypervisor] section
+ below.
+
+#html.hr()
+
+= Build
+
+== Server
+
+I'm using my original
+#extlink("https://frame.work/ca/en/products/mainboard-11th-gen-intel-core?v=FRANFG000B")[Framework
+ 13] laptop.
+
+#fig("framework-server/case.jpg", alt: "case image")
+
+Its an i7-1165G7 - yes a "mobile" CPU. Am I asking for trouble? We'll see. Its
+more than powerful enough for me (for now).
+
+Alternatively you could build/use another server.
+
+If you are also using the Framework 13, then you'll want to make sure that you
+are running at least *BIOS 3.17*. See the #link(<issues>)[issues] section below
+about my troubles.
+
+== Case
+
+As you can see above, I opted for the
+#extlink("https://frame.work/ca/en/products/cooler-master-mainboard-case")[Coolermaster
+ Case] because honestly it looks cool.
+
+Alternatively, you could self-print the
+#extlink("https://github.com/FrameworkComputer/Framework-Laptop-13")[community]
+3D case or get someone else to print it.
+
+== Network Connectivity
+
+I opted for the 2.5G
+#extlink("https://frame.work/ca/en/products/ethernet-expansion-card")[ethernet
+ expansion card] from Framework.
+
+#fig("framework-server/eth.jpg", alt: "framework eth adapter image")
+
+I suppose one could also use a WiFi card, but it makes more sense to go wired
+in a 24/7 "on" configuration.
+
+Alternatively, most thunderbolt-ethernet dongles should do the job. You'd want
+to check that there are Linux drivers for it.
+
+Since I host a few publicly accessible services (this blog for example), I also
+need a public static IP under which an internet user can access these services.
+
+So I opt for a cheap VPS from
+#extlink("https://www.racknerd.com/kvm-vps")[Racknerd]. These are about a \$1/mo
+for a public IPv4 IP, and a decent amount of network bandwidth.
+
+#fig("framework-server/vps.png", alt: "racknerd vps pricing image")
+
+== Storage
+
+=== for VMs
+
+I used the 1TB NVME drive I had on my Framework.
+
+=== for Data
+
+_You should have a storage solution (NAS) in place already!_
+
+#fig("framework-server/nas.jpg", alt: "nas image")
+
+Setting this up is beyond the scope of this post. You can check out my DIY ZFS
+backed NAS setup
+#extlink("https://gitlab.com/kdam0/vps#nas-equipment")[here]. I've had no
+(storage related) issues with my NAS since its inception over a year ago.
+
+Then when I need to allocate space for a new service I do:
+
+```bash
+zfs create <pool>/<dataset>
+zfs set compression=on/off
+zfs set sharenfs="..." sharesmb=".."
+```
+
+Then when the time comes, configure the NFS mount on the client VM that
+requires access to this data store:
+
+```bash
+mount -t nfs hostname-of-your-nas:/mnt/pool/dataset /mnt/data
+```
+
+There are also many options for pre-made NAS appliances such as Synology.
+
+== Power
+
+Highly recommend a UPS such as
+#extlink("https://www.amazon.ca/APC-BE600M1-Back-Uninterrupted-Electronics-Computers/dp/B01FWAZEIU/ref=sr_1_6?crid=28ZH7JP9IYXPU&keywords=ups&qid=1690928556&sprefix=ups%2Caps%2C172&sr=8-6")[this].
+
+#fig("framework-server/ups.jpg", alt: "ups image")
+
+Even if you don't care about uptime, *you should care about data integrity*
+issues resulting from abrupt power-loss.
+
+A UPS should buy you enough time to power things down gracefully.
+
+= Hypervisor <hypervisor>
+
+I opted for
+#extlink("https://www.proxmox.com/en/proxmox-virtual-environment/overview")[Proxmox
+ VE] since v8 was just released, I've been itching to try it out. Now felt
+like the right time.
+
+There is also a Terraform
+#extlink("https://registry.terraform.io/providers/Telmate/proxmox/latest/docs")[provider]
+allowing provisioning of VMs, and LXC Containers through code. Neat!
+
+#quote(block: true)[
+ I recommend you do this *before* taking the mainboard out of the laptop
+ chassis as you will need to provide keyboard input, as well as see whats
+ going on the screen.
+]
+
+- Install it. I just followed the
+ #extlink("https://www.proxmox.com/en/proxmox-virtual-environment/get-started")[official
+ instructions].
+
+- Under your host (mine's `pve`), under System, Network, create a bridge
+ `vmbr0` with:
+
+#fig("framework-server/net1.png", alt: "proxmox net image")
+
+This will make your Proxmox host available in your home network. *Set this to
+something else if you like.*
+
+#quote(block: true)[
+ You should also reserve the corresponding IP in your home router for this
+ host.
+]
+
+While you're here, create another bridge `vmbr1` with `VLAN Aware` checked:
+
+#fig("framework-server/net2.png", alt: "proxmox net image")
+
+#quote(block: true)[
+ We are going to create our home-lab network primarily on `vmbr1`. Think of
+ `vmbr0` as a _management_ bridge.
+]
+
+= Network
+
+I always find it best to sort out networking *before* we start building stuff.
+
+Since I'm basically starting over, I want to do it properly. A few design
+goals:
+
++ I want segregation of my home-lab services from my non-home-lab hosts (my
+ "workstation" VM for example), as well as the rest of my home network.
++ I don't want to keep track of static IP leases, everything should be DHCP,
+ and DNS.
++ I want all traffic to/from my home-lab segment to be encrypted. Non-home-lab
+ traffic does not need to be encrypted.
++ I need some way to make these services publicly accessible over the internet.
+
+The first two goals necessitate a virtual firewall/router deployment capable of
+_selectively_ routing a segment of my network over a VPN.
+
+Here is what I would like:
+
+#fig("framework-server/network.png", alt: "network architecture")
+
+== Goals: 1,2
+
+At first I went with the popular pfSense VM. For a #link(<pfsense>)[few]
+reasons, I abandoned it quickly.
+
+#html.hr()
+
+Then I decided to try out OpenWRT, and boy am I glad I did.
+
+#html.hr()
+
+I gave it two networking interfaces, each `vbmrX` we created earlier:
+
+#fig("framework-server/pfnet.png", alt: "openwrt bridge image")
+
+- `vmbr0` will be used to access our router from the home-network. It will also
+ be how our router gets access to the internet. Aka. *WAN*.
+- `vmbr1` will be used to create various subnets for our home-lab. Aka. *LAN*.
+
+#quote(block: true)[
+ You should reserve another IP in your home router using the MAC address
+ generated for `net0`. This will be how to access your home-lab router from
+ your home network.
+]
+
+I was able to setup a network with a few VLANs:
+
+#fig("framework-server/wrtnet.png", alt: "openwrt network image")
+
+#quote(block: true)[
+ This means that the next time we create a new VM or LXC container, we can
+ place it on `vmbr1`, with a particular VLAN Tag/`id` and it will start with
+ the corresponding IP for that network.
+]
+
+Few observations:
+
+- The Luci web UI is much more minimal and to the point. The terminology used
+ for things are more in-line with what I'm familiar with.
+- Soon I had the segregation I was looking for thanks to Firewall settings:
+ #fig("framework-server/fw1.png", alt: "firewall settings image")
+- DHCP, *and* DNS working as expected.
+
+== Goals: 3
+
+Then I started working on setting up my VPN (Wireguard) remote on my VPS.
+
+- I followed the script from
+ #extlink("https://github.com/angristan/wireguard-install")[here]. Worked
+ flawlessly.
+- Added a peer through the script - my OpenWRT VM.
+- Setup a domain - eg. `vpn.mydomain.com` to point to my VPS's public IP.
+- Then I enabled the "DMZ" setting (but *not* "Advanced DMZ" - which is
+ probably the _real_ DMZ) on my home ISP router settings, and added my OpenWRT
+ VM to it.
+ #fig("framework-server/dmz.png", alt: "dmz image")
+- I also setup a port-forward for the Wireguard port 51820 from my ISP router
+ settings, to point to my OpenWRT VM's internal port 51820.
+ #fig("framework-server/port1.png", alt: "port forward image")
+
+After all that, I started setting up Wireguard on OpenWRT.
+
+- The corresponding OpenWRT package is called `luci-app-wireguard`.
+- This will give you the ability to create a Wireguard interface on OpenWRT.
+ #fig("framework-server/wg.png", alt: "wg menu image")
+
+- I followed the following excellent tutorial for setting up the Wireguard
+ interface, configured to use my VPS as its peer:
+ #extlink("https://www.youtube.com/watch?v=04q41GEPvKA")[Setting up Wireguard
+ on OpenWRT]
+- Created the new interface and added the firewall settings:
+ #fig("framework-server/wrtnet2.png", alt: "openwrt wg ifrace")
+ #fig("framework-server/fw2.png", alt: "firewall wg settings image")
+
+- As is, this will mean that *only* LAB will have internet access (using the
+ Wireguard interface) - which is not what I want. I still want other subnets
+ to be routed via *WAN*.
+
+*_If only there was a way to route by VLANs..._*
+
+#quote(block: true)[
+ This is known as VPN _split-tunnelling_, or _Policy based routing_.
+]
+
+Luckily OpenWRT has a way to accomplish exactly this with a package called
+`luci-app-pbr`. This will add the PBR setting to your menu:
+
+#fig("framework-server/pbr.png", alt: "pbr setting image")
+
+- I followed the following tutorial:
+ #extlink("https://www.youtube.com/watch?v=FN2qfxNIs2g")[Setting up PBR on
+ OpenWRT]
+
+- Set up the rule for my PBR:
+ #fig("framework-server/pbr2.png", alt: "pbr rule image")
+
+- The default interface is set to `wan`, so everything else should behave as
+ normal:
+ #fig("framework-server/pbr1.png", alt: "pbr global gateways image")
+
+However, it did take many trail & error sessions (see the
+#link(<pbr-dns-leakage>)[dns leakage issues] section below) to get this working
+exactly as I wanted.
+
+*In summary*,
+
+When I create a VM/container in my _Lab_ subnet,
+
+- its external IP is that of my VPS
+- when it reaches out to the internet, it goes through the Wireguard tunnel
+ encrypted, to the VPS, and out the VPS's ISP.
+ #fig("framework-server/lab.png", alt: "lab flow image")
+
+When I create a VM/container *not* in my Lab subnet,
+
+- its external IP is the same as my home-network's public IP
+- when it reaches out to the internet, it goes through my home ISP (NAT), just
+ like all other devices on my home-network.
+ #fig("framework-server/lan.png", alt: "lan flow image")
+
+PBR provides quite granular control over this, for example, if you run a Plex
+server, it frequently needs to access the `plex.tv` domain. You can choose to
+route any source going to `plex.tv` to route via the `wan` interface instead.
+
+== Goals: 4
+
+For publicly accessible services, I need to point their domains to my VPS. I
+use sub-domains, so this is just a matter of creating `CNAME` records for these
+services.
+
+Then we setup our Wireguard on the VPS to forward all 80,443 TCP traffic to our
+Wireguard on OpenWRT. We'd need to add these lines *in addition* to your
+existing VPS `wg0` interface config:
+
+```
+PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 80 -j DNAT --to-destination <ip of your OpenWRT wg0>:80
+PostUp = iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 443 -j DNAT --to-destination <ip of your OpenWRT wg0>:443
+...
+PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 80 -j DNAT --to-destination <ip of your OpenWRT wg0>:80
+PostDown = iptables -t nat -D PREROUTING -p tcp -i eth0 --dport 443 -j DNAT --to-destination <ip of your OpenWRT wg0>:443
+```
+
+Then we also need to setup port-forwards for 80,443 on OpenWRT going to our
+Reverse Proxy VM/Container.
+
+#fig("framework-server/port2.png", alt: "port forwards image")
+
+I just setup a temporary Caddy container in reverse-proxy mode to test this
+out.
+
+#fig("framework-server/rproxy.png", alt: "reverse-proxy setup image")
+
+Once this works, we can move on to building out our services :)
+
+= Home Lab Services
+
+These are _some_ of the services I run ad LXC container:
+
+- #extlink("https://nextcloud.com/athome/")[Nextcloud] - files, and image
+ storage and sharing (thanks to the excellent
+ #extlink("https://apps.nextcloud.com/apps/memories")[Memories] app). I used
+ the official #extlink("https://github.com/nextcloud/all-in-one")[AIO]
+ install.
+- #extlink("https://www.plex.tv/")[Plex] - media streaming.
+- #extlink("https://github.com/dani-garcia/vaultwarden")[Vaultwarden] - a
+ self-hosted password manager based on Bitwarden.
+- A few static websites.
+
+How I set up these services is beyond the scope of this post. You should check
+out my git repo's #extlink("https://gitlab.com/kdam0/home-lab")[readme] for
+instructions. I will just briefly summarize my process here.
+
+- I use LXC wherever possible as it has lower overhead than VMs.
+- LXC or VM hosts are provisioned by Terraform and services are configured by
+ Ansible.
+
+One thing to note is that as of now, there is no automated way to pass-through
+my iGPU to my LXC containers that would benefit from this - such as Nextcloud
+(for Memories app), Plex etc.
+
+Instead you must edit the config on Proxmox for the corresponding container,
+and add the following lines and restart the container. Edit
+`/etc/pve/lxc/<id of your container>.conf`:
+
+```
+lxc.cgroup2.devices.allow: c 226:0 rwm
+lxc.cgroup2.devices.allow: c 226:128 rwm
+lxc.cgroup2.devices.allow: c 29:0 rwm
+lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir
+lxc.mount.entry: /dev/dri/renderD128 dev/renderD128 none bind,optional,create=file
+```
+
+Then inside your LXC container do `ls -al /dev/dri/` and you should see some
+devices.
+
+#fig("framework-server/dri.png", alt: "gpu devices image")
+
+= Issues <issues>
+
+== Intermittent connectivity loss with Framework
+
+- When I initially started this project, I had already installed Proxmox, and
+ my setup my networking while on BIOS 3.16.
+- However every couple of days (sometimes hours) I was observing loss of
+ connectivity to both my Proxmox host, and my virtual router.
+- When I plugged in a monitor to see what was going on, I saw a few messages on
+ the TTY regarding `usbX disconnected...`.
+- Unplugging and re-plugging in the ethernet card did not fix it.
+- Usually a hard reset was required to bring things back to normal.
+
+I've encountered issues with other Framework peripherals in the past, so I
+reached out to the Framework support folks, and they immediately suggested a
+BIOS update.
+
+So I proceeded to give that a shot from within Proxmox using `fwupmgr`:
+
+#fig("framework-server/fail.png", alt: "bios up fail image")
+
+And to my surprise it was complaining about _battery level_? Its not even
+connected to the battery...
+
+I brought this up to the support and they confirmed my fear that it was not
+possible to update the BIOS while having the battery detached. Yikes!
+
+This was a deal-breaker for this whole project, until they confirmed that once
+I was on 3.17, it would be possible to do future updates without the battery
+being connected.
+
+- So I went ahead and pretty much attached the whole thing back into the laptop
+ chassis
+- Updated to 3.17, and applied "Standalone operation"
+ #fig("framework-server/bios2.png", alt: "bios update image")
+
+- Thankfully, all my connectivity issues have gone away now.
+- Framework support service is excellent!
+
+== Intermittent loss of internet for hosts in Lab Vlan.
+
+This one kept me up for many nights. To summarize:
+
+- in a host in the Lab network, doing an `apt update` the first time would
+ hang, mid-way through
+- running it a few times again, would eventually succeed the operation
+ #fig("framework-server/wtf.jpg", alt: "wtf meme")
+
+Could it be DNS? Could it be PBR? Could it be Wireguard?
+
+After many hours of troubleshooting, I happened to have seen errors during a
+`tcpdump` session on my VPN interface.
+
+Something about `...need to fragment...`. DOH!
+
+- Lab internet access is via VPN (due to PBR).
+- *VPN remote interface has an MTU of 1420, not 1500.*
+- Proxmox `vmbr1` set MTU to 1500 by default.
+- Therefore all hosts in Lab have a MTU mismatch trying to get to the internet.
+
+#quote(block: true)[Fixed. It was not DNS... it was MTU.]
+
+== PBR DNS leakage <pbr-dns-leakage>
+
+The whole point having my Lab traffic go through Wireguard is to prevent my
+home ISP from knowing what I am up to. *DNS queries leaking to my ISP
+completely defeats this purpose!*
+
+Although my external IP would correctly report to being my VPS one, tests like
+#extlink("https://github.com/macvk/dnsleaktest")[this] would show that DNS was
+still being sent to my home ISP.
+
+#fig("framework-server/dns.jpg", alt: "dns meme image")
+
+To fix this, I had to:
+
+- *Un-check* setting default route on the Lab interface.
+ #fig("framework-server/fix1.png", alt: "dns fix 1 image")
+
+- Explicitly set DHCP DNS settings for my Lab gateway interface to use my VPS's
+ `wg0` interface IP.
+ #fig("framework-server/fix2.png", alt: "dns fix 2 image")
+
+- Needed a static route defied for the `wg0` interface targeting the VPN
+ network via the OpenWRT local IP for `wg0` as a gateway.
+ #fig("framework-server/fix3.png", alt: "dns fix 3 image")
+
+== pfSense <pfsense>
+
+Few observations:
+
+- Segregation was working how I wanted it to.
+- A few firewall rules were required to allow access to parts of my
+ home-network - NAS.
+- DHCP, worked out of the box.
+- However, DNS did not.
+
+I'm still not sure what the issue was, but it was probably something small.
+
+More importantly, I was quite overwhelmed with all the options in the UI for
+which I had no knowledge of.
+
+Also, everything in pfSense pretty much _requires_ a UI, which for now is fine,
+but I'd like the option to move to a more automation friendly configuration in
+the future.
+
+= Summary
+
+The Framework laptop is happily fulfilling its destiny as a compute server, and
+so is my NAS as a storage server.
+
+Since switching to this setup, I've observed a dramatic improvement in
+responsiveness for all of my services.
+
+#fig("framework-server/imp.png", alt: "improvement meme image")
+
+Overall I'm quite pleased with how everything turned out once the appropriate
+BIOS was running on the Framework.
+]
diff --git a/post/framework-server/bios.jpg b/post/framework-server/bios.jpg
new file mode 100755
index 0000000..76cda35
--- /dev/null
+++ b/post/framework-server/bios.jpg
Binary files differ
diff --git a/post/framework-server/bios2.png b/post/framework-server/bios2.png
new file mode 100755
index 0000000..094e37e
--- /dev/null
+++ b/post/framework-server/bios2.png
Binary files differ
diff --git a/post/framework-server/case.jpg b/post/framework-server/case.jpg
new file mode 100755
index 0000000..a40c172
--- /dev/null
+++ b/post/framework-server/case.jpg
Binary files differ
diff --git a/post/framework-server/dmz.png b/post/framework-server/dmz.png
new file mode 100755
index 0000000..372c12b
--- /dev/null
+++ b/post/framework-server/dmz.png
Binary files differ
diff --git a/post/framework-server/dns.jpg b/post/framework-server/dns.jpg
new file mode 100755
index 0000000..0b32528
--- /dev/null
+++ b/post/framework-server/dns.jpg
Binary files differ
diff --git a/post/framework-server/dri.png b/post/framework-server/dri.png
new file mode 100755
index 0000000..a578c79
--- /dev/null
+++ b/post/framework-server/dri.png
Binary files differ
diff --git a/post/framework-server/error.jpg b/post/framework-server/error.jpg
new file mode 100755
index 0000000..429eef8
--- /dev/null
+++ b/post/framework-server/error.jpg
Binary files differ
diff --git a/post/framework-server/eth.jpg b/post/framework-server/eth.jpg
new file mode 100755
index 0000000..f8ad12a
--- /dev/null
+++ b/post/framework-server/eth.jpg
Binary files differ
diff --git a/post/framework-server/fail.png b/post/framework-server/fail.png
new file mode 100755
index 0000000..f0e06db
--- /dev/null
+++ b/post/framework-server/fail.png
Binary files differ
diff --git a/post/framework-server/feelsbadman.png b/post/framework-server/feelsbadman.png
new file mode 100755
index 0000000..b1c2a26
--- /dev/null
+++ b/post/framework-server/feelsbadman.png
Binary files differ
diff --git a/post/framework-server/fix1.png b/post/framework-server/fix1.png
new file mode 100755
index 0000000..b36489e
--- /dev/null
+++ b/post/framework-server/fix1.png
Binary files differ
diff --git a/post/framework-server/fix2.png b/post/framework-server/fix2.png
new file mode 100755
index 0000000..5b64900
--- /dev/null
+++ b/post/framework-server/fix2.png
Binary files differ
diff --git a/post/framework-server/fix3.png b/post/framework-server/fix3.png
new file mode 100755
index 0000000..9af2433
--- /dev/null
+++ b/post/framework-server/fix3.png
Binary files differ
diff --git a/post/framework-server/fw1.png b/post/framework-server/fw1.png
new file mode 100755
index 0000000..72a95ee
--- /dev/null
+++ b/post/framework-server/fw1.png
Binary files differ
diff --git a/post/framework-server/fw2.png b/post/framework-server/fw2.png
new file mode 100755
index 0000000..cec00e1
--- /dev/null
+++ b/post/framework-server/fw2.png
Binary files differ
diff --git a/post/framework-server/imp.png b/post/framework-server/imp.png
new file mode 100755
index 0000000..c11b806
--- /dev/null
+++ b/post/framework-server/imp.png
Binary files differ
diff --git a/post/framework-server/lab.png b/post/framework-server/lab.png
new file mode 100755
index 0000000..6591a8b
--- /dev/null
+++ b/post/framework-server/lab.png
Binary files differ
diff --git a/post/framework-server/lan.png b/post/framework-server/lan.png
new file mode 100755
index 0000000..461d598
--- /dev/null
+++ b/post/framework-server/lan.png
Binary files differ
diff --git a/post/framework-server/mistakes.jpg b/post/framework-server/mistakes.jpg
new file mode 100755
index 0000000..d4b2e12
--- /dev/null
+++ b/post/framework-server/mistakes.jpg
Binary files differ
diff --git a/post/framework-server/nas.jpg b/post/framework-server/nas.jpg
new file mode 100755
index 0000000..75f5251
--- /dev/null
+++ b/post/framework-server/nas.jpg
Binary files differ
diff --git a/post/framework-server/net1.png b/post/framework-server/net1.png
new file mode 100755
index 0000000..a0a4eff
--- /dev/null
+++ b/post/framework-server/net1.png
Binary files differ
diff --git a/post/framework-server/net2.png b/post/framework-server/net2.png
new file mode 100755
index 0000000..c5ad118
--- /dev/null
+++ b/post/framework-server/net2.png
Binary files differ
diff --git a/post/framework-server/network.png b/post/framework-server/network.png
new file mode 100755
index 0000000..1b1fba6
--- /dev/null
+++ b/post/framework-server/network.png
Binary files differ
diff --git a/post/framework-server/pbr.png b/post/framework-server/pbr.png
new file mode 100755
index 0000000..2b1e330
--- /dev/null
+++ b/post/framework-server/pbr.png
Binary files differ
diff --git a/post/framework-server/pbr1.png b/post/framework-server/pbr1.png
new file mode 100755
index 0000000..4d7f075
--- /dev/null
+++ b/post/framework-server/pbr1.png
Binary files differ
diff --git a/post/framework-server/pbr2.png b/post/framework-server/pbr2.png
new file mode 100755
index 0000000..0a07839
--- /dev/null
+++ b/post/framework-server/pbr2.png
Binary files differ
diff --git a/post/framework-server/pfnet.png b/post/framework-server/pfnet.png
new file mode 100755
index 0000000..67cc5f4
--- /dev/null
+++ b/post/framework-server/pfnet.png
Binary files differ
diff --git a/post/framework-server/physical.png b/post/framework-server/physical.png
new file mode 100755
index 0000000..fc1cb81
--- /dev/null
+++ b/post/framework-server/physical.png
Binary files differ
diff --git a/post/framework-server/port1.png b/post/framework-server/port1.png
new file mode 100755
index 0000000..1b72ab1
--- /dev/null
+++ b/post/framework-server/port1.png
Binary files differ
diff --git a/post/framework-server/port2.png b/post/framework-server/port2.png
new file mode 100755
index 0000000..37ff887
--- /dev/null
+++ b/post/framework-server/port2.png
Binary files differ
diff --git a/post/framework-server/rproxy.png b/post/framework-server/rproxy.png
new file mode 100755
index 0000000..0d3f0bf
--- /dev/null
+++ b/post/framework-server/rproxy.png
Binary files differ
diff --git a/post/framework-server/ups.jpg b/post/framework-server/ups.jpg
new file mode 100755
index 0000000..4cd7aee
--- /dev/null
+++ b/post/framework-server/ups.jpg
Binary files differ
diff --git a/post/framework-server/vps.png b/post/framework-server/vps.png
new file mode 100755
index 0000000..7392495
--- /dev/null
+++ b/post/framework-server/vps.png
Binary files differ
diff --git a/post/framework-server/wg.png b/post/framework-server/wg.png
new file mode 100755
index 0000000..18896a0
--- /dev/null
+++ b/post/framework-server/wg.png
Binary files differ
diff --git a/post/framework-server/wrtnet.png b/post/framework-server/wrtnet.png
new file mode 100755
index 0000000..73d435d
--- /dev/null
+++ b/post/framework-server/wrtnet.png
Binary files differ
diff --git a/post/framework-server/wrtnet2.png b/post/framework-server/wrtnet2.png
new file mode 100755
index 0000000..34cd6b9
--- /dev/null
+++ b/post/framework-server/wrtnet2.png
Binary files differ
diff --git a/post/framework-server/wtf.jpg b/post/framework-server/wtf.jpg
new file mode 100755
index 0000000..995fefe
--- /dev/null
+++ b/post/framework-server/wtf.jpg
Binary files differ
diff --git a/post/listing-my-fav-advice.typ b/post/listing-my-fav-advice.typ
new file mode 100644
index 0000000..5984f97
--- /dev/null
+++ b/post/listing-my-fav-advice.typ
@@ -0,0 +1,87 @@
+#import "/global/common.typ": *
+
+#let doc = [
+I outline my favourite life advice, where I have heard it, and what it means to
+me.
+
+= Everything in moderation, including moderation.
+
+#table(
+ columns: 2,
+ stroke: none,
+ align: center,
+ img("listing-my-fav-advice/balance.jpg", alt: "balancing rocks"),
+ img("listing-my-fav-advice/indulgence.jpg", alt: "indulgence painting"),
+)
+
+I first heard this one from Kia, my Jitsu instructor at U of T. The first part
+is fairly self-explanatory. The second part says to moderate the moderation. In
+other words, on occasion, it is ok to indulge. After all, some of the best
+moments in life come from indulgence. Don't be the person who is always
+moderating, or you will miss out on 100% of those moments.
+
+This reminds me of a paradigm in Computation called
+#extlink("https://en.wikipedia.org/wiki/Reinforcement_learning")[Reinforcement
+ Learning] (RL), where learning is maximized by balancing existing knowledge,
+with occasional spurts of exploration. Without exploration, we find that we are
+generally unlikely to end up at an optimal solution, thus showing us its
+importance.
+
+If the objective of life is to attain long-term (cumulative) happiness, then we
+can say that moderation is the current knowledge, and occasional indulgence are
+the spurts of exploration, and both must be balanced in order to maximize our
+objective.
+
+= Chew your food. Well.
+
+#fig("listing-my-fav-advice/chewing.gif", alt: "chewing")
+
+This one I got from my grandparents, and it is quite underrated. We all know
+that our digestion health is such a large contributor to our overall
+well-being. Yet we religiously neglect the first step of the process - chewing.
+
+I specifically recall my grandpa saying "_chew it until it becomes a paste_".
+Gross but effective. No matter what your diet is composed of, its not debatable
+that chewing better will only ever help your digestion, never harm.
+
+Another benefit? Sitting down and consciously chewing requires time and effort.
+If you aren't doing it, maybe its a sign that you are rushing. Why are you
+rushing? Maybe its time to re-evaluate some things in your life.
+
+= Don't worry so much about budgeting, focus on earning more.
+
+#fig("listing-my-fav-advice/money.jpg", alt: "money bob ross")
+
+This one is somewhat controversial. I heard it from John Hill on the
+#extlink("https://podcasts.apple.com/us/podcast/super-hoopers-an-nba-podcast/id1053263719")[Super
+ Hoopers podcast], quoting some book. I think this stems from the notion that
+there is a hard limit to how much you can restrict your spending to - \$0. But
+no limit to how much you can earn. So why not focus on that?
+
+I think this is largely a time-management principle. Maybe you've heard the
+idiom "penny wise, and pound foolish". If money is the _penny_, then your time
+is the _pound_. Don't be foolish with your time.
+
+People often will go out of their way believing they are saving a certain
+amount of money but hardly ever factor in the value of the time spent saving
+that amount. And more often than not, that time is spent doing something that
+is not enjoyable.
+
+That same time could've been spent on trying to earn more money than what was
+saved or at the very least doing something actually enjoyable.
+
+= Never work for someone who you don't want to become.
+
+#fig("listing-my-fav-advice/boss.jpg", alt: "boss", width: "50%")
+
+This one has helped me substantially in my career. I believe I heard it from
+someone on Shark Tank (Mark Cuban? Mr. Wonderful? IIRC). More important than
+finding the right role/career is finding the right mentor. I've personally
+switched roles based on this advice, and I can attribute most my success as a
+direct result of this.
+
+We tend to emulate people that we are influenced by. By working for such a
+manager, you are setting yourself up to emulate someone you look up to - which
+is a great thing! I also consider this one of the few advantages of being an
+employee rather than an owner. Don't waste it!
+]
diff --git a/post/listing-my-fav-advice/balance.jpg b/post/listing-my-fav-advice/balance.jpg
new file mode 100755
index 0000000..ee5050c
--- /dev/null
+++ b/post/listing-my-fav-advice/balance.jpg
Binary files differ
diff --git a/post/listing-my-fav-advice/boss.jpg b/post/listing-my-fav-advice/boss.jpg
new file mode 100755
index 0000000..631a78a
--- /dev/null
+++ b/post/listing-my-fav-advice/boss.jpg
Binary files differ
diff --git a/post/listing-my-fav-advice/chewing.gif b/post/listing-my-fav-advice/chewing.gif
new file mode 100755
index 0000000..12b7d16
--- /dev/null
+++ b/post/listing-my-fav-advice/chewing.gif
Binary files differ
diff --git a/post/listing-my-fav-advice/indulgence.jpg b/post/listing-my-fav-advice/indulgence.jpg
new file mode 100755
index 0000000..446e3fb
--- /dev/null
+++ b/post/listing-my-fav-advice/indulgence.jpg
Binary files differ
diff --git a/post/listing-my-fav-advice/money.jpg b/post/listing-my-fav-advice/money.jpg
new file mode 100755
index 0000000..451ff7c
--- /dev/null
+++ b/post/listing-my-fav-advice/money.jpg
Binary files differ
diff --git a/post/nas-upgrade.typ b/post/nas-upgrade.typ
new file mode 100644
index 0000000..9b48839
--- /dev/null
+++ b/post/nas-upgrade.typ
@@ -0,0 +1,148 @@
+#import "/global/common.typ": *
+
+#let doc = [
+Doing it right this time.
+
+Until now I've been using a
+#extlink("https://pine64.org/devices/rockpro64/")[RockPro64] in the (excellent
+for starters) Pine64 #extlink("https://wiki.pine64.org/wiki/NASCase")[NAS case]
+for my NAS. It runs #extlink("https://www.armbian.com/rockpro64/")[Armbian],
+`openzfs`, and I manage my ZFS as needed on the CLI. I share my datasets over
+NFS to my Proxmox guest VMs for access. This has served me reasonably well for
+the past few years. But we have a baby coming soon, and along with that a
+flurry of new photos and videos for my family to store on my NAS. And although
+I _probably_ have enough buffer to survive the initial few months, I certainly
+wish to do better than my current 2xHDD mirrored pool giving me ~4TB of
+storage.
+
+#fig("nas-upgrade/oldnas.png", alt: "nas nas")
+
+= The Plan
+
+Luckily I already possess the hard-drives I need for my desired pool. In total,
+I have 2 HDDs from my current NAS, and 2 spares - all the same 4TB capacity.
+
+I still want to keep my current NAS running as a backup store, but I only need
+1 drive for this purpose (for now). That leaves me with 3 HDDs we can use for
+our new `raidz1` pool which would give me 2 drives for storage and 1 for
+parity.
+
+Now for the platform. I have been a very happy user of
+#extlink("https://www.proxmox.com/en/proxmox-virtual-environment/overview")[Proxmox]
+(on my compute node) and it already comes ready with ZFS support so I'll stick
+with that. I did consider #extlink("https://www.truenas.com")[TrueNAS] (both
+standalone, and virtualized within Proxmox with disk-passthrough), but when it
+comes to storage, I want as few surprises as possible so I stuck with what I
+know. I'm already used to ZFS on the CLI and Proxmox is based on Debian so
+if/when something goes wrong, I don't want to be messing around with an
+unfamiliar UI.
+
+Although I don't want to manage ZFS via a GUI, I'd like a way to manage
+_access_ to my datasets via a GUI.
+#extlink("https://cockpit-project.org")[Cockpit] seems like a good light-weight
+choice. It runs as an LXC container within Proxmox using bind-mounts for the
+dataset paths. I can then select which paths I allow access to over my network
+using NFS, and SMB protocols on a per-user basis.
+
+= Shopping
+
+Needs:
+
+- `>= 3` hot-swappable HDD bays
+- `>= 16` GB RAM (for ZFS)
+- runs Proxmox
+
+Wants:
+
+- IPMI
+- 1U
+- 10Gb networking
+
+On eBay, I ended up checking out with:
+
+#fig("nas-upgrade/nas-chassis.png", alt: "nas chassis")
+#fig("nas-upgrade/ethernet.png", alt: "ethernet adapter")
+
+#table(
+ columns: 2,
+ [Part], [Price (shipped)],
+ [1U 20" Short Depth Supermicro Server X9SCL-F XEON E3-1270 V2 16GB NIC Rails], [\$236],
+ [Mellanox ConnectX-2 PCIe x8 10Gbe SFP+ network card], [\$23],
+)
+
+which gave me everything I needed *and* wanted!
+
+= Build
+
+First I free one of my HDDs from my RockPro64 NAS for use in my new pool. This
+means I am running on a single HDD for the remainder of the migration.
+
+With the free HDD, and my two spares, I get everything assembled, and set up a
+new Proxmox node with a ZFS pool from the UI using the 3 disks.
+
+#fig("nas-upgrade/zfs-pool.png", alt: "proxmox zfs pool")
+
+= Sync
+
+To migrate all of my data from the old pool to the new pool I use
+#extlink("https://github.com/jimsalterjrs/sanoid/tree/master?tab=readme-ov-file#syncoid")[`syncoid`].
+It uses ZFS snapshots to accomplish this. It's awesome.
+
+This takes many hours depending on the size of your pool and your connection,
+but since I saw almost full utilization of my network link I was convinced it's
+the best I can do right now.
+
+I've seen in a few Reddit posts suggesting ways this can be optimized using
+some combination of `zfs send` with `mbuffer` rather than `ssh` which `syncoid`
+uses, but I'm happy with the convenience of `syncoid`.
+
+The best part is that everything can still be running while this is going on!
+This is because only the first sync takes a long time since it needs to copy
+_everything_ over. All subsequent syncs only transfer the delta since the last
+sync which should be pretty quick if your data does not change all that much.
+
+That being said, since I am now running on a single drive, and hitting it hard
+during sync, I do not want to stay in this state for any longer than I need to
+due to potentially leading my single disk to failure.
+
+Once the sync is complete, I do a second sync (for the delta), and promptly
+shut down all of my services to prepare for the cut-over.
+
+= Cockpit
+
+Before I can re-enable my services. I need to expose my datasets on Cockpit.
+
+I create a Debian LXC, and assign the generated MAC a static IP on my network.
+Other than that, I just make sure my container has the NFS feature enabled:
+
+#fig("nas-upgrade/cockpit-lxc.png", alt: "cockpit lxc features")
+
+Setting up user permissions is a pain, but once done I expose the relevant NFS
+as well as Samba paths for my services - replicating my old NAS's shared paths.
+
+= Voila
+
+In my homelab nameserver, I update the A record for my NAS to point to the new
+IP of my Cockpit LXC container, and start my services back up.
+
+I confirm everything is still good by turning off the old NAS, and yep still
+good.
+
+Luckily for me all good on the first try!
+
+= Finishing touches
+
+I set up a cron on my old NAS to sync data nightly from the new pool. This will
+only work while the actual data size on my new pool is less than 4TB, so I'll
+need to get a new drive with higher capacity to keep the backups going in the
+future.
+
+Luckily I should have about a year until I exceed 4TB on the new pool so I'll
+be keeping an eye on deals to snag an 8TB backup drive along with a few spares
+for my new NAS.
+
+= Resources
+
+- #extlink("https://www.apalrd.net/posts/2023/ultimate_nas/")[https://www.apalrd.net/posts/2023/ultimate_nas/]
+- #extlink("https://blog.kye.dev/proxmox-cockpit")[https://blog.kye.dev/proxmox-cockpit]
+]
diff --git a/post/nas-upgrade/cockpit-lxc.png b/post/nas-upgrade/cockpit-lxc.png
new file mode 100755
index 0000000..a2a9363
--- /dev/null
+++ b/post/nas-upgrade/cockpit-lxc.png
Binary files differ
diff --git a/post/nas-upgrade/ethernet.png b/post/nas-upgrade/ethernet.png
new file mode 100755
index 0000000..f39110d
--- /dev/null
+++ b/post/nas-upgrade/ethernet.png
Binary files differ
diff --git a/post/nas-upgrade/nas-chassis.png b/post/nas-upgrade/nas-chassis.png
new file mode 100755
index 0000000..5d70b60
--- /dev/null
+++ b/post/nas-upgrade/nas-chassis.png
Binary files differ
diff --git a/post/nas-upgrade/oldnas.png b/post/nas-upgrade/oldnas.png
new file mode 100755
index 0000000..15ad609
--- /dev/null
+++ b/post/nas-upgrade/oldnas.png
Binary files differ
diff --git a/post/nas-upgrade/zfs-pool.png b/post/nas-upgrade/zfs-pool.png
new file mode 100755
index 0000000..ecf9605
--- /dev/null
+++ b/post/nas-upgrade/zfs-pool.png
Binary files differ
diff --git a/post/nixos-p1.typ b/post/nixos-p1.typ
new file mode 100644
index 0000000..2d248c8
--- /dev/null
+++ b/post/nixos-p1.typ
@@ -0,0 +1,459 @@
+#import "/global/common.typ": *
+
+#let doc = [
+Almost a guide to getting started with NixOS the modern (2023) way.
+
+There seems to be a shortage of written guides on the Internet for setting up
+NixOS the "modern" way - Flakes + Home Manager. It doesn't help that Nix's
+official docs are very disjointed so hopefully this will fill in some gaps that
+I observed when setting it all up.
+
+#fig("nixos-p1/fox.jpg", alt: "meme")
+
+#quote(block: true)[
+ Warning: NixOS is not exactly beginner friendly - you should have
+ familiarity installing Linux distros before trying this.
+]
+
+= But Why NixOS?
+
+Eh... FOMO regarding all the
+#extlink("https://www.google.com/search?q=nixos+memes&tbm=isch")[memes].
+
+There is one particular feature that is intriguing - the ability to roll-back
+your entire system (not incl. BIOS) in case of a misconfiguration or a broken
+update. *You can't do this with Ansible.* This allows you (in theory) to get
+the benefits of a rolling release as well as the stability benefits of a
+traditional distro.
+
+#quote(block: true)[
+ It takes the idea of reproducible builds, and extends it to the OS.
+]
+
+For example, NixOS will present you with all previous "builds" at boot time for
+you to revert to in case something gets messed up.
+
+#fig("nixos-p1/boot.png", alt: "boot prompt")
+
+= Things I want to explore as part of this exercise:
+
++ How hard is it to go from a minimal install to productive? At minimum I need
+ a graphical environment with working vol, mic, camera, wifi, and hibernation.
+ Is it harder than doing the same in something like Manjaro?
++ Is Wayland truly ready?
++ Try out #extlink("https://github.com/djpohly/dwl")[`dwl`] (the Wayland port
+ of `dwm` by the #extlink("https://suckless.org")[suckless] folks)
+
+= Constraints
+
++ I want to stick to the "Nix" way of doing things as much as possible _where
+ it makes sense to me_.
++ Stick to Wayland only applications as much as possible.
+
+= Spoilers! (End result)
+
+#fig("nixos-p1/rice.png", alt: "my rice")
+
+(Probably too ugly for r/unixporn, but works for me :)
+
+= Installation
+
+== Getting a Live USB going
+
+- I used the #extlink("https://nixos.org/download.html#nixos-iso")[minimal iso].
+- Create a bootable USB with the usual - `sudo dd if=/path/to/iso of=/dev/sdX bs=4M`.
+- Boot up.
+- Then start following the steps for
+ #extlink("https://nixos.org/manual/nixos/stable/index.html#sec-installation-manual")[manual
+ installation] from the official guide to complete the install.
+
+#quote(block: true)[
+ Make sure to give SWAP as much space as your memory capacity for
+ hibernation to work properly.
+]
+
+#quote(block: true)[
+ Wifi did not work for me out of the box. So I used my phone to tether via
+ USB.
+]
+
+= First boot
+
+If everything went well, you should be presented with a TTY prompting you to
+login:
+
+```
+NixOS ...
+Login: <your username>
+Password: <your password>
+```
+
+Once you login, you will still just have a TTY, but we can now go ahead and
+start installing our graphical environment.
+
+= Housekeeping
+
+- You should have two NixOS config files in `/etc/nixos/`:
+
+```
+configuration.nix
+hardware-configuration.nix
+```
+
+- Edit the `configuration.nix` file by setting the correct values for hostname,
+ networking, timezone and users. I also add a few basic system-wide packages
+ here such as `git, rsync, neovim, htop` etc.
+
+- To _apply and use_ your changes to any of these files you need to run:
+
+```bash
+sudo nixos-rebuild switch
+```
+
+- Since we want all our configuration to be version controlled, I copied these
+ files to live under my user's config: `~/.config/{nix, nixpkgs}/`:
+
+```bash
+cp /etc/nix/nix.conf ~/.config/nix/nix.conf
+cp /etc/nixos/configuration.nix ~/.config/nixpkgs/configuration.nix
+```
+
+Nix will now use these user-specific files to read its config :)
+
+== Flakes Support
+
+Nix (the pkg mgr) does not come with Flakes support out-of-the-box. So we need
+to enable it:
+
++ In `~/.config/nix/nix.conf` add:
+
+```
+experimental-features = nix-command flakes
+```
+
++ Apply it:
+
+```bash
+sudo nixos-rebuild switch
+```
+
+Read about #extlink("https://nixos.wiki/wiki/Flakes")[Flakes].
+
+== Home Manager Support
+
+Now we can install the Home-Manager flake.
+
++ Init our base flake:
+
+```bash
+cd ~/.config/nixpkgs
+nix flake init
+```
+
+This should generate two files:
+
+```
+flake.nix
+flake.lock
+```
+
++ Next we tell `flake.nix` to manage all our configuration (system + home) for
+ our system:
+
+```nix
+{
+ description = "NixOS configuration";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ home-manager.url = "github:nix-community/home-manager";
+ home-manager.inputs.nixpkgs.follows = "nixpkgs";
+ nixos-hardware.url = "github:NixOS/nixos-hardware/master";
+ };
+
+ outputs = inputs@{ nixpkgs, home-manager, nixos-hardware, ... }: {
+ nixosConfigurations = {
+ # Change below to use your hostname from configuration.nix
+ "art-sr" = nixpkgs.lib.nixosSystem {
+ system = "x86_64-linux";
+ modules = [
+ ./configuration.nix
+ nixos-hardware.nixosModules.framework
+ home-manager.nixosModules.home-manager
+ {
+ home-manager.useGlobalPkgs = true;
+ home-manager.useUserPackages = true;
+ # Change below to use your username from configuration.nix
+ home-manager.users.kdam0 = import ./home.nix;
+
+ # Optionally, use home-manager.extraSpecialArgs to pass
+ # arguments to home.nix
+ }
+ ];
+ };
+ };
+ };
+}
+```
+
+#quote(block: true)["art-sr" is my hostname for this machine.]
+
+In my case, I also have the following two lines specifically to load settings
+for my hardware:
+
+```
+ nixos-hardware.url = "github:NixOS/nixos-hardware/master";
+ ...
+ nixos-hardware.nixosModules.framework
+```
+
+you will need to modify these values based on your
+#extlink("https://github.com/NixOS/nixos-hardware")[hardware support].
+
++ Create a `~/.config/nixpkgs/home.nix` file with your values:
+
+```nix
+{ config, pkgs, ...}:
+{
+ home.username = "kdam0";
+ home.homeDirectory = "/home/kdam0";
+
+ programs.home-manager.enable = true;
+ home.stateVersion = "22.11";
+
+ services.gpg-agent = {
+ enable = true;
+ defaultCacheTtl = 1800;
+ enableSshSupport = true;
+ };
+}
+```
+
++ Apply:
+
+```bash
+sudo nixos-rebuild switch
+```
+
+#quote(block: true)[
+ It took a few attempts to get NixOS to re-build successfully. I ran into a
+ few different issues involving users, and hostnames while following the
+ official docs until I arrived at the configs above which worked.
+]
+
+Read about
+#extlink("https://nix-community.github.io/home-manager/index.html#ch-nix-flakes")[Home
+ Manager flake].
+
+*This would be a good time init a git repo in `~/.config/nixpkgs/` and publish
+your progress.*
+
+= Setting up the GUI
+
+== Window Manager + bar + system info.
+
+Getting `dwl` is easy enough. In my `home.nix`:
+
+```
+ home.packages = [
+ pkgs.dwl
+ ...
+ ];
+```
+
+This will install `dwl` on a rebuild. Then I can run it with `dwl`.
+
+Oh it fails...something about permissions... In
+`~/.config/nixpkgs/configuration.nix` make sure you have:
+
+```
+ security.polkit.enable = true;
+```
+
+Oh it fails again with GLE errors :( Add:
+
+```
+ hardware.opengl = {
+ enable = true;
+ driSupport = true;
+ };
+```
+
+*Sweet now it launches!*
+
+But of course I need to configure the keys - that's the whole point of a WM.
+Additionally, `dwl` requires a re-build on every config change...
+
+*How do I tell Nix to use my config file while installing/building `dwl`?*
+
+Lucking all packages (`pkgs.*`) build files are defined on their GitHub. We can
+see that we are allowed to pass in a `conf` argument to
+#extlink("https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/window-managers/dwl/default.nix")[this]
+file.
+
+After a bit of digging around, I arrived at how to do it:
+
++ First copy your `config.h` to `~/.config/nixpkgs/dwl/config.h`.
++ Then point to it in your `home.nix`:
+
+```
+ home.packages = [
+ (pkgs.dwl.override {
+ # trying to supply config.home.homeDirectory here leads to "impure" usage.
+ # so disabling it for now.
+ # conf = (builtins.readFile "${config.home.homeDirectory}/.config/dwl/config.h");
+ conf = ./dwl/config.h;
+ })
+ ...
+ ];
+```
+
+#quote(block: true)[
+ I know I can just clone the source myself and build it, but I like to use
+ the default package manager whenever possible to manage packages.
+]
+
+Of course since `dwl` is as minimal as it gets, it does not ship with a bar. We
+have many options for which bar to use, I have very little use for a bar, so I
+kept it very simple and went with `somebar`:
+
+```
+ home.packages = [
+ ...
+ pkgs.somebar
+ ...
+ ];
+```
+
+If you want status info on your bar you can use something like `someblocks` -
+which will let you script simple scripts with text output you want displayed in
+each block. This will need to be cloned and built manually as it is not
+available in the Nix repos.
+
+Oh but you probably don't have `make` or any requirements to actually build
+it...fear not:
+
+```bash
+nix-shell -p gnumake
+```
+
+which put you in a temporary environment with all the common build tools
+available. Now you can:
+
+```bash
+sudo make install
+```
+
+`wbg` is a simple background setter for Wayland......aaaand BAM!
+
+#fig("nixos-p1/dwl.png", alt: "dwl pic")
+
+== Terminal
+
+I use #extlink("https://codeberg.org/dnkl/foot")[`foot`].
+
+Create the config file in `~/.config/nixpkgs/foot/foot.ini`:
+
+```
+# for transparency #
+[colors]
+alpha=0.7
+```
+
+Use it in `home.nix`:
+
+```
+ home.file.".config/foot/foot.ini".source = ../../common/foot/foot.ini;
+```
+
+#quote(block: true)[I use this pattern for pretty much all my _dotfiles_:]
+
+```
+ # script that sets a bg.
+ home.file."bg.sh".source = common/bg.sh;
+ # script that starts my gui env.
+ home.file."start.sh".source = common/start.sh;
+ # foot config
+ home.file.".config/foot/foot.ini".source = common/foot/foot.ini;
+ # wofi config (app launcher)
+ home.file.".config/wofi/style.css".source = common/wofi/style.css;
+ # mako config (notifications)
+ home.file.".config/mako/config".source = common/mako/config;
+```
+
+== Sound
+
+In my `configuration.nix`:
+
+```
+ security.rtkit.enable = true;
+ services.pipewire = {
+ enable = true;
+ alsa.enable = true;
+ alsa.support32Bit = true;
+ pulse.enable = true;
+ wireplumber.enable = true;
+ media-session.enable = false;
+ jack.enable = true;
+ systemWide = false;
+ };
+```
+
+== Nextcloud
+
+I want a purely CLI way to handle this, and a periodic sync is sufficient for
+me. We do this with `systemd-timers` (Nix advises against `cron`). In my
+`home.nix`:
+
+```
+ systemd.user.services = {
+ nextcloud-sync = {
+ Unit = {
+ Description = "Auto sync Nextcloud";
+ After = "network-online.target";
+ };
+ Service = {
+ Type = "simple";
+ EnvironmentFile = "${config.home.homeDirectory}/.nextcloud.env";
+ ExecStart = ''
+ ${pkgs.nextcloud-client}/bin/nextcloudcmd \
+ -h --non-interactive \
+ --user "''${NEXTCLOUD_USER}" \
+ --password "''${NEXTCLOUD_PASSWORD}" \
+ ''${NEXTCLOUD_DIR} \
+ ''${NEXTCLOUD_URL}
+ '';
+ TimeoutStopSec = "180";
+ KillMode = "process";
+ KillSignal = "SIGINT";
+ };
+ Install.WantedBy = ["multi-user.target"];
+ };
+ };
+ systemd.user.timers = {
+ nextcloud-sync = {
+ Unit.Description = "Automatic sync files with Nextcloud when booted up after 5 minutes then rerun every 10 minutes";
+ Timer.OnUnitActiveSec = "10min";
+ Install.WantedBy = ["multi-user.target" "timers.target"];
+ };
+ };
+ systemd.user.startServices = true;
+```
+
+= Conclusions
+
++ Yes setting things up from a minimal iso is harder than in Manjaro. Although
+ I got everything I wanted working, there were many times I felt like giving
+ up. (Aside: I am _so grateful_ for Manjaro, and Arch wikis). That said, I
+ expect this friction is a one-time cost for building familiarity with Nix,
+ and well worth the benefits that come with it.
++ You bet Wayland is ready. Multi-monitor works out-of-the-box, all my apps
+ support it, and things just _feel_ more polished than I have ever felt with
+ Xorg.
++ `dwl` is as awesome as I had hoped! This is my daily driver now.
+
+= My configs
+
+All the configs discussed (and more) are in my
+#extlink("https://gitlab.com/kdam0/dotfiles-nix")[nixdotfiles repo].
+]
diff --git a/post/nixos-p1/boot.png b/post/nixos-p1/boot.png
new file mode 100755
index 0000000..4e3d972
--- /dev/null
+++ b/post/nixos-p1/boot.png
Binary files differ
diff --git a/post/nixos-p1/dwl.png b/post/nixos-p1/dwl.png
new file mode 100755
index 0000000..8ecbe4d
--- /dev/null
+++ b/post/nixos-p1/dwl.png
Binary files differ
diff --git a/post/nixos-p1/fox.jpg b/post/nixos-p1/fox.jpg
new file mode 100755
index 0000000..3ace57f
--- /dev/null
+++ b/post/nixos-p1/fox.jpg
Binary files differ
diff --git a/post/nixos-p1/rice.png b/post/nixos-p1/rice.png
new file mode 100755
index 0000000..4fd97cf
--- /dev/null
+++ b/post/nixos-p1/rice.png
Binary files differ
diff --git a/post/programming-as-art.typ b/post/programming-as-art.typ
new file mode 100644
index 0000000..ef80e33
--- /dev/null
+++ b/post/programming-as-art.typ
@@ -0,0 +1,98 @@
+#import "/global/common.typ": *
+
+#let doc = [
+Why there is art in programming.
+
+During my early days as a CS student, one of the first mind-blowing moments was
+watching `Hello World!` getting printed out to the console thousands of times
+in just two functional lines of code.
+
+```python
+for _ in range(1, 1001):
+ print("Hello World!")
+```
+
+At the time it felt like having the
+#extlink("https://harry-potter-compendium.fandom.com/wiki/Elder_Wand")[Elder Wand].
+
+#fig("programming-as-art/wand.png", alt: "Dumbledore with Elder Wand pic")
+
+But there was more to our lesson. The TA then asks us to put our newly found
+power to use by computing the sum of 1 to 100. Of course, it was a natural
+application of what we had just done earlier:
+
+```python
+sum = 0
+for i in range(1, 100+1):
+ sum = sum + i
+```
+
+Sure enough we saw the answer `5050` in the console. But then the TA reminds us
+that we are making our computers *work too hard*. In other words, the computer
+needs to do one-hundred ADD instructions in order to make this computation
+happen.
+
+What if the number was a million? How well would our method scale?
+
+Well then it would take a million ADD instructions. We call this scaling
+_linearly_ with the input size. Later we would formalize this to $cal(O)(n)$
+(pronounced: _Big Oh of N_).
+
+The TA hinted that there is a better way, and that we already know of the
+better way in math.
+
+#fig("programming-as-art/teaching.png", alt: "Teaching meme")
+
+$ S_n = sum_(i=1)^n i = 1 + 2 + ... + n = (n (n + 1)) / 2 $
+
+With this we are no longer using loops, but a known mathematical fact about
+sequences. If you don't belive me, see the
+#extlink("https://letstalkscience.ca/educational-resources/backgrounders/gauss-summation")[proof].
+
+Written as code:
+
+```python
+sum = n (n + 1) / 2
+```
+
+This one-liner solves our problem with just 3 (ADD, MULTIPLY, DIVIDE)
+instructions. Crucially, it does not depend on the size of the input like our
+previous solution, thus *no matter the input, it always takes 3 instructions to
+compute!* This is a HUGE win!
+
+#fig("programming-as-art/math.png", alt: "Math meme")
+
+Later we would formalize this to $cal(O)(1)$, or _constant_ scaling.
+
+#quote(block: true)[
+ Yes, yes I know IRL the complier would optimize the loop solution such
+ that it does not take N instructions but for the purposes of learning we
+ were not allowed to depend on that.
+]
+
+Looking back at it now, both solutions are equally correct, and modern
+compilers would optimize the first solution in the final instructions sent to
+the cpu, such that any performance differences would be negligible. In other
+words, the computer wouldn't acutally be _working so hard_.
+
+Objectively, the first solution is more readable, and friendly to a new
+observer than the second.
+
+_Why then am I still so drawn to the second solution?_
+
+The first solution reminds me of the saying "to a hammer, eveything looks like
+a nail". Its a brute force approach. In comparison, the second solution is
+using the exact tool for our particular problem. It somehow feels personalized
+and dare I say _romantic_.
+
+When I reflect on moments like this, it reminds me that there is emergent
+elegance and beauty even in the seemingly arbitrary sequence of symbols that is
+`code`.
+
+#fig("programming-as-art/code.png", alt: "The Matrix code going by image")
+
+Programming is not quite as _objective_ as people would have you believe. There
+are trade-offs to each solution, and which solution you prefer relect on the
+trade-offs you are willing to accept, which varies by the observer: much like
+_art_.
+]
diff --git a/post/programming-as-art/code.png b/post/programming-as-art/code.png
new file mode 100755
index 0000000..30c3522
--- /dev/null
+++ b/post/programming-as-art/code.png
Binary files differ
diff --git a/post/programming-as-art/math.png b/post/programming-as-art/math.png
new file mode 100755
index 0000000..bbdc372
--- /dev/null
+++ b/post/programming-as-art/math.png
Binary files differ
diff --git a/post/programming-as-art/teaching.png b/post/programming-as-art/teaching.png
new file mode 100755
index 0000000..9bf5dce
--- /dev/null
+++ b/post/programming-as-art/teaching.png
Binary files differ
diff --git a/post/programming-as-art/wand.png b/post/programming-as-art/wand.png
new file mode 100755
index 0000000..5eefc9c
--- /dev/null
+++ b/post/programming-as-art/wand.png
Binary files differ
diff --git a/post/programs.typ b/post/programs.typ
new file mode 100644
index 0000000..fb675f7
--- /dev/null
+++ b/post/programs.typ
@@ -0,0 +1,103 @@
+#import "/global/common.typ": *
+
+#let doc = [
+The programs and equipment I use on a daily basis.
+
+= Programs and Equipment I Use
+
+I'm about getting things done quickly and having as little latency between my
+thoughts and actions on the computer.
+
+I like having vim-like bindings and prefer running programs in the terminal for
+simplicity's sake. That said, I'm very much against the cringe meme that things
+in the terminal are "cooler" or "nerdier". Terminals are good for most tasks,
+but useless for others, for example, browsing the web (I admit this unfortunate
+fact with much consternation) or looking at maps.
+
+== Software I Use
+
+=== OS Distribution
+
+#extlink("https://nixos.org/")[NixOS]. NixOS is an immutable OS where you are
+only allowed to modify the system via declarative config files.
+
+I've distro-hopped between Manjaro, Void, Debian, and Fedora, but since NixOS
+this has largely stopped as I'm quite happy with my NixOS + Home Manager setup.
+I did like Debian, and Fedora as well.
+
+You can find my configs #extlink("https://gitlab.com/kdam0/dotfiles-nix")[here].
+There is a dedicated #pagelink("post/nixos-p1")[post] about this.
+
+=== Desktop Environment
+
+I've settled on #extlink("https://github.com/djpohly/dwl")[DWL] after many
+years of hopping. Its clean, functional, and extremely light.
+
+Prior to this, I've used Bsmpw + Sxhkd, i3, Gnome, KDE Plasma etc. I'm glad
+these exist and people get use out of it, but for now I do not miss it.
+
+#fig("programs/dwl.png", alt: "DWL")
+
+=== Text Editor
+
+#extlink("https://neovim.io/")[(neo)vim]. Less of a text editor and more of a
+lifestyle. Check out my dotfiles for this. No, I'm not going to ever switch to
+emacs.
+
+#fig("https://neovim.io/images/showcase/telescope_helptags.png", alt: "Neovim")
+
+=== Web Browser
+
+#extlink("https://www.mozilla.org/en-US/firefox/features/")[Firefox] /
+#extlink("https://brave.com")[Brave] /
+#extlink("https://vivaldi.com")[Vivaldi]. I have tried Chromium, Qutebrowser in
+the past.
+
+=== File Manager
+
+#extlink("https://github.com/ranger/ranger")[ranger]. Yes, I've tried `nnn` and
+others. Yes I know its not the fastest, but something about Ranger makes me
+keep coming back to it.
+
+#fig("https://raw.githubusercontent.com/ranger/ranger-assets/master/screenshots/twopane.png", alt: "ranger")
+
+== Where can I find good software options?
+
+The program of your dreams is probably listed below:
+
+- The #extlink("https://suckless.org/rocks/")[suckless] website's list of
+ programs that "rock". Generally minimalist programs.
+- A more comprehensive
+ #extlink("https://github.com/mayfrost/guides/blob/master/ALTERNATIVES.md")[list]
+ of minimalist software.
+- #extlink("https://directory.fsf.org/wiki/Main_Page")[FSF's Free Software
+ Directory]. Emphasis on libre software (although most software in the links
+ above will have free licenses as well).
+- #extlink("https://wiki.installgentoo.com/index.php/List_of_recommended_GNU/Linux_software")[Gentoo
+ Wiki Recommendations] A good mix of programs for novices and advanced
+ users.
+
+== Hardware I Use
+
+=== Laptop
+
+#strike[The main laptop I use is the
+#extlink("https://frame.work/")[Framework laptop]. I'm a huge fan of the whole
+upgradability concept coming from a few generations of ThinkPads.]
+
+I've since re-purposed this laptop to be
+#pagelink("post/framework-server")[something else].
+
+== What I don't use
+
+Proprietary software.
+
+I'm not going to endorse proprietary services that have gone out of their way
+to spy on or politically suppress their users, just as Facebook, Discord etc.
+One of the many take-aways you should get from me is that the use of
+libre/free software, by its nature, is more constructive and extensible; and
+*that's the point*.
+
+There are philosophical reasons for this I talk about
+#pagelink("post/self-host")[here].
+]
diff --git a/post/programs/dwl.png b/post/programs/dwl.png
new file mode 100755
index 0000000..8ecbe4d
--- /dev/null
+++ b/post/programs/dwl.png
Binary files differ
diff --git a/post/self-host.typ b/post/self-host.typ
new file mode 100644
index 0000000..0fed382
--- /dev/null
+++ b/post/self-host.typ
@@ -0,0 +1,138 @@
+#import "/global/common.typ": *
+
+#let doc = [
+My reasons for self-hosting.
+
+= Digital freedom/independence
+
+#fig("self-host/freedom.jpg", alt: "Freedom pic")
+
+To put it simply, if you use a service such as Lastpass, iCloud, Dropbox,
+GDrive, OneDrive, etc. to store your data, *you do not own it*.
+
+You are _trusting_ corp. X to store it, secure it, and make it available to
+you. Beyond the obvious privacy pitfalls with this, there are many other things
+that could go wrong with your data being stored this way:
+
+- Corp. X could vanish/close/"declare bankruptcy" (as with many crypto corps.
+ lately), and you lose your data.
+- Corp. X could make a policy change and lock you out from accessing your
+ account.
+- Or you could "forget" to pay the bills a few times and get locked out.
+- Corp. X could get hacked, and your data gets stolen. This is the latest
+ trend!
+- Many other doomsday scenarios.
+
+Personally, I don't feel comfortable depending on the existence of corp. X
+_for data that is important to me_ i.e. a lifetime's worth of documents,
+memories, and passwords etc. I sleep much better at night knowing I am avoiding
+most if not all of the risks above.
+
+= Privacy <privacy>
+
+Normies often ask me some variation of:
+
+- Why do I care if corp. X has my data? I have nothing to hide!
+- Since I'm aware of targeted advertising, it won't work on me, so why should
+ I care if corp. X has my data?
+
+#fig("self-host/why.jpg", alt: "my pic", width: "75%")
+
+To such questions, I follow-up with:
+
+#quote(block: true)[*Do you want to contribute to a dystopian
+ (#extlink("https://en.wikipedia.org/wiki/Orwellian")[Orwellian]) future?*]
+
+#fig("self-host/police.jpg", alt: "thought police", width: "75%")
+
+If your answer is _yes_, then no need to read further, the following will not
+change your mind.
+
+If your answer is _no_, but you aren't convinced that/how these are related,
+then the _argument goes something like this..._
+
+- We live in a world of mass data-collection/surveillance.
+- This enables corp. X to use techniques such as ML (Machine Learning) to
+ build/train models (or "AI" - Artificial Intelligence) that aim to predict
+ human behaviour.
+- That by itself is not a problem here.
+- The problems arise when corp. X use these techniques for profit without
+ regard for the harm they cause at a _population level_.
+
+I have unfortunately lived through enough of these cases to be able to cite a
+few recent examples:
+
+- Instagram (etc.) designs its apps/products optimizing for maximum addiction
+ (see
+ #extlink("https://sitn.hms.harvard.edu/flash/2018/dopamine-smartphones-battle-time/")[Smartphones
+ and dopamine]). Think slot-machines. You might think you are "too smart"
+ to fall for these tricks, and you might be right, but what about the millions
+ of adolescents using these platforms who aren't as wise as you? The data
+ collected, and techniques developed from _your usage_ enables platforms to
+ target not only you, but _all_ users on the platform, including the most
+ vulnerable.
+
+ Unfortunately, many of these kids will end up suffering from mental-health,
+ body-image, self-esteem issues (see
+ #extlink("https://onlinedegrees.unr.edu/online-master-of-public-health/impact-of-social-media-on-youth-mental-health/")[Mental
+ health and social media]), and for some it *will be fatal*. I do not
+ think this is morally acceptable, and is primary reason I refuse to
+ participate in social-media platforms.
+
+ #fig("self-host/6imv05.jpg", alt: "Depression")
+
+You could say "well that's due to bad parenting", but the point remains that
+more and more of the digital world is having _real world_ negative consequences
+regardless of your individual participation level. Another example:
+
+- By now its well known that Facebook's (etc.) targeted advertising played a
+ significant a role in the 2016 US Elections (see
+ #extlink("https://www.theverge.com/2017/12/11/16761016/former-facebook-exec-ripping-apart-society")[Excerpt
+ from Facebook ex-exec]). Facebook was able to do this thanks to its users
+ voluntarily giving personal information for two decades. However, the
+ policies that come out of this election has real world consequences to
+ millions (if not billions) of people, regardless of their _individual_
+ Facebook usage, which undermines the very purpose of a democratic republic.
+ Regardless of your political affiliation, it should worry you that this
+ _can_ happen.
+
+ #fig("self-host/elections.jpg", alt: "Elections")
+
+To summarize, it might not be a problem if individuals disregard their privacy
+at the individual level, but in aggregate, a _population-wide_ disregard has
+dystopian consequences.
+
+So the question you have to ask yourself is:
+
+#quote(block: true)[*What can I do?*]
+
+Start by *valuing your privacy*. Then, follow some of these tips to _say no..._
+
+- *Limit your exposure to these services.* If a friend stops talking to you
+ because of this, then congratulations, you've just gotten rid of a fake
+ friend. You're welcome.
+- #extlink("https://wiki.r-selfhosted.com/getting-started/what-is-self-hosting/")[Self-host]
+ as much as you can. You can see how I
+ #extlink("https://gitlab.com/kdam0/vps")[implement] this and replicate it
+ yourself, though it is a bit involved for normies.
+- If neither of the above work for you, then you'll need to do some research to
+ find an alternative source that you can trust isn't doing the same thing. Use
+ #extlink("https://github.com/awesome-selfhosted/awesome-selfhosted")[this]
+ as a starting point and try to search (Ctrl+f) for your service. For eg.
+ Twitter, and Instagram both have popular options available.
+- *Stop using Chrome.* If you must use it, use
+ #extlink("https://brave.com/")[Brave] instead. I advocate for
+ #extlink("https://www.mozilla.org/en-US/firefox/new/?redirect_source=firefox-com")[Firefox].
+ Neither are perfect, but both offer privacy respecting options in their
+ settings. Specifically disabling cross-site tracking is important. Beyond
+ that, install an ad-blocker extension such as
+ #extlink("https://ublockorigin.com/")[UBlock Origin] on both of them. Don't
+ forget to do the same on your mobile device!
+- Things get more complicated on cell-phones. I suggest switching to a privacy
+ respecting operating-system such as
+ #extlink("https://grapheneos.org/")[GrapheneOS]. Short of this, you can only
+ limit your usage.
+- Support government policies that respect user-privacy.
+
+#fig("self-host/office.png", alt: "office pic", width: "75%")
+]
diff --git a/post/self-host/6imv05.jpg b/post/self-host/6imv05.jpg
new file mode 100755
index 0000000..d042d7a
--- /dev/null
+++ b/post/self-host/6imv05.jpg
Binary files differ
diff --git a/post/self-host/elections.jpg b/post/self-host/elections.jpg
new file mode 100755
index 0000000..ac4ac96
--- /dev/null
+++ b/post/self-host/elections.jpg
Binary files differ
diff --git a/post/self-host/freedom.jpg b/post/self-host/freedom.jpg
new file mode 100755
index 0000000..d62048a
--- /dev/null
+++ b/post/self-host/freedom.jpg
Binary files differ
diff --git a/post/self-host/office.png b/post/self-host/office.png
new file mode 100755
index 0000000..9cff3ce
--- /dev/null
+++ b/post/self-host/office.png
Binary files differ
diff --git a/post/self-host/police.jpg b/post/self-host/police.jpg
new file mode 100755
index 0000000..dde2b44
--- /dev/null
+++ b/post/self-host/police.jpg
Binary files differ
diff --git a/post/self-host/why.jpg b/post/self-host/why.jpg
new file mode 100755
index 0000000..eda5402
--- /dev/null
+++ b/post/self-host/why.jpg
Binary files differ
diff --git a/site.typ b/site.typ
new file mode 100644
index 0000000..4bca4bb
--- /dev/null
+++ b/site.typ
@@ -0,0 +1,68 @@
+// Site structure and metadata for kumardamani.net.
+
+#let site-info = (
+ pages: (
+ index: (title: "Home"),
+ software: (title: "Software"),
+ photos: (
+ title: "Photos",
+ url: "https://nc.kumardamani.net/apps/memories/a/vSk9IAWVUx2vcqphL9nMnLFmDQBDZWPr",
+ ),
+ contact: (title: "Contact"),
+
+ "post/framework-server-p2": (
+ title: "Framework Laptop Again",
+ long-title: "Letting my Framework 13 be a laptop again",
+ date: "2024-06-03",
+ description: "Graduating to a more conventional home-lab.",
+ hidden: true,
+ ),
+ "post/nas-upgrade": (
+ title: "NAS Upgrade",
+ date: "2024-06-01",
+ description: "I upgrade my NAS hardware, and the platform it runs on.",
+ hidden: true,
+ ),
+ "post/listing-my-fav-advice": (
+ title: "Favourite Life Advice",
+ long-title: "A list of my favourite life advice",
+ date: "2023-09-01",
+ description: "A list of my favourite life advice.",
+ hidden: true,
+ ),
+ "post/framework-server": (
+ title: "Framework Server",
+ long-title: "Helping my Framework Laptop reach its destiny",
+ date: "2023-07-29",
+ description: "Framework laptop as a standalone server.",
+ hidden: true,
+ ),
+ "post/nixos-p1": (
+ title: "NixOS Part 1",
+ long-title: "Adventures in NixOS - Part 1",
+ date: "2023-03-24",
+ description: "Guide to getting started with NixOS the modern way.",
+ hidden: true,
+ ),
+ "post/programming-as-art": (
+ title: "Romanticism in Programming",
+ date: "2022-11-29",
+ description: "How programming is romantic.",
+ hidden: true,
+ ),
+ "post/self-host": (
+ title: "Why Self-Host?",
+ long-title: "Why do I self-host?",
+ date: "2022-09-12",
+ description: "Overview of why I self-host.",
+ hidden: true,
+ ),
+ "post/programs": (
+ title: "Programs and Equipment",
+ date: "2021-11-12",
+ description: "Overview for the programs and equipment I use day-to-day.",
+ hidden: true,
+ ),
+ ),
+ title: "~kumar",
+)
diff --git a/software.typ b/software.typ
new file mode 100644
index 0000000..bd3c50e
--- /dev/null
+++ b/software.typ
@@ -0,0 +1,69 @@
+#import "/global/common.typ": *
+
+#let source(dest) = html.p(class: "source", extlink(dest)[source])
+#let description(body) = html.p(class: "description", body)
+
+#let doc = [
+ Here you can find a list of my note-worthy public repositories, all hosted
+ on #extlink("https://gitlab.com/kdam0/")[GitLab].
+
+ #show list: prev => html.ul(class: "biglist", {
+ for c in prev.children {
+ html.li(c.body)
+ }
+ })
+
+ -
+ = nixdots
+ #description[
+ My dotfiles for the Nix ecosystem, as started in
+ #pagelink("post/nixos-p1")[this post].
+ ]
+ #source("https://gitlab.com/kdam0/dotfiles-nix")
+ -
+ = home-lab
+ #description[
+ My Terraform, Ansible repository for self-hosting, as described in
+ #pagelink("post/framework-server")[this post].
+ ]
+ #source("https://gitlab.com/kdam0/home-lab")
+ -
+ = nccli
+ #description[
+ A Docker-ized Golang CLI for CRUD ops for Namecheap's DNS settings.
+ ]
+ #source("https://gitlab.com/kdam0/nccli")
+ -
+ = vps
+ #description[
+ How I implement #pagelink("post/self-host")[self-hosting], including
+ my DIY ZFS backed NAS setup.
+ ]
+ #source("https://gitlab.com/kdam0/vps")
+ -
+ = go-druid-mysql-influx
+ #description[
+ A fast, containerized cron script that interacts with Druid, MySql,
+ and Influxdb written in Go, with basic unit testing.
+ ]
+ #source("https://gitlab.com/kdam0/go-druid-mysql-influx")
+ -
+ = devops-api
+ #description[
+ A containerized, highly modular REST API that writes to Influxdb.
+ ]
+ #source("https://gitlab.com/kdam0/devops-api")
+ -
+ = slack-to-teams
+ #description[
+ A Python script to migrate Slack exports to MS Teams messages.
+ ]
+ #source("https://gitlab.com/kdam0/slack-to-teams")
+ -
+ = random-shell-helpers
+ #description[
+ A small collection of helper scripts that I used frequently for
+ random things.
+ ]
+ #source("https://gitlab.com/kdam0/random-shell-helpers")
+]