diff --git a/assets/css/custom.css b/assets/css/custom.css index 63a3ddd..7e14fc4 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,6 +1,6 @@ .max-w-prose { - max-width:120ch - } + max-width:240ch +} .panzoom-popup { position: fixed; @@ -51,4 +51,131 @@ width: auto; /* Allow it to size naturally */ max-width: 100%; /* Prevent overflow */ } - \ No newline at end of file + +.prose :where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color:var(--tw-prose-links); + text-decoration:underline; + font-weight:500; + text-decoration-color:rgba(var(--color-secondary-700), 1); + text-decoration-thickness:1px; + text-decoration-style:wavy; +} + +.prose nav a { + color:var(--tw-prose-links); + text-decoration:none; + font-weight:500; + text-decoration-color:rgba(var(--color-primary-300), 1); +} + +.dark\:prose-invert:is(.dark *) :where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) { + text-decoration-color:rgba(var(--color-secondary-700), 1); +} + +.prose { + --tw-prose-body:rgba(var(--color-neutral-700), 1); + --tw-prose-headings:rgba(var(--color-neutral-800), 1); + --tw-prose-lead:rgba(var(--color-neutral-500), 1); + --tw-prose-links:rgba(var(--color-secondary-600), 1); + --tw-prose-bold:rgba(var(--color-neutral-900), 1); + --tw-prose-counters:rgba(var(--color-neutral-800), 1); + --tw-prose-bullets:rgba(var(--color-neutral-500), 1); + --tw-prose-hr:rgba(var(--color-neutral-200), 1); + --tw-prose-quotes:rgba(var(--color-neutral-700), 1); + --tw-prose-quote-borders:rgba(var(--color-primary-200), 1); + --tw-prose-captions:rgba(var(--color-neutral-500), 1); + --tw-prose-kbd:#111827; + --tw-prose-kbd-shadows:17 24 39; + --tw-prose-code:rgba(var(--color-secondary-700), 1); + --tw-prose-pre-code:rgba(var(--color-neutral-700), 1); + --tw-prose-pre-bg:rgba(var(--color-neutral-50), 1); + --tw-prose-th-borders:rgba(var(--color-neutral-500), 1); + --tw-prose-td-borders:rgba(var(--color-neutral-300), 1); + --tw-prose-invert-body:rgba(var(--color-neutral-300), 1); + --tw-prose-invert-headings:rgba(var(--color-neutral-50), 1); + --tw-prose-invert-lead:rgba(var(--color-neutral-500), 1); + --tw-prose-invert-links:rgba(var(--color-secondary-600), 1); + --tw-prose-invert-bold:rgba(var(--color-neutral), 1); + --tw-prose-invert-counters:rgba(var(--color-neutral-400), 1); + --tw-prose-invert-bullets:rgba(var(--color-neutral-600), 1); + --tw-prose-invert-hr:rgba(var(--color-neutral-500), 1); + --tw-prose-invert-quotes:rgba(var(--color-neutral-200), 1); + --tw-prose-invert-quote-borders:rgba(var(--color-primary-900), 1); + --tw-prose-invert-captions:rgba(var(--color-neutral-400), 1); + --tw-prose-invert-kbd:#fff; + --tw-prose-invert-kbd-shadows:255 255 255; + --tw-prose-invert-code:rgba(var(--color-secondary-400), 1); + --tw-prose-invert-pre-code:rgba(var(--color-neutral-200), 1); + --tw-prose-invert-pre-bg:rgba(var(--color-neutral-700), 1); + --tw-prose-invert-th-borders:rgba(var(--color-neutral-500), 1); + --tw-prose-invert-td-borders:rgba(var(--color-neutral-700), 1); + font-size:1rem; + line-height:1.75 +} + +.prose :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top:1.25em; + margin-bottom:1.25em; +} + +/* +p { + background-color:rgba(var(--color-primary-900), 0.5); + border-radius:2px; + padding-right:4px; + padding-left:4px; +} */ + +.prose :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color:var(--tw-prose-captions); + font-size:0.875em; + line-height:1.4285714; + margin-top:0.8571429em +} + +/* .grow :where(main){ + flex-grow:1; + background-color:rgba(var(--color-primary-900), 0.5); + border-width:2px; + border-style:dashed; + border-radius:2px; + border-color:rgba(var(--color-secondary-600), 1); + padding-right:8px; + padding-left:8px; + padding-top:8px; +} .*/ + +.article-content { + background-color:rgba(var(--color-primary-900), 0.5); + border-width:2px; + border-style:dashed; + border-radius:2px; + border-color:rgba(var(--color-secondary-600), 1); + padding-right:8px; + padding-left:8px; + padding-bottom:8px; + border-top-color:transparent; +} + +#single_header { + flex-grow:1; + background-color:rgba(var(--color-primary-900), 0.5); + border-width:2px; + border-style:dashed; + border-radius:2px; + border-color:rgba(var(--color-secondary-600), 1); + padding-right:8px; + padding-left:8px; + padding-top:8px; + border-bottom-color:transparent; +} + +.prose .chroma:is(.dark *) { + background-color:rgba(var(--color-primary-900), 0.8); + --tw-text-opacity:1; + color:rgba(var(--color-neutral-200), var(--tw-text-opacity)) +} + +.dark\:prose-invert:is(.dark *) :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + background-color:rgba(var(--color-primary-900), 0.8); +} \ No newline at end of file diff --git a/assets/js/panzoom-util.js b/assets/js/panzoom-util.js index 3a3025e..89bd396 100644 --- a/assets/js/panzoom-util.js +++ b/assets/js/panzoom-util.js @@ -90,8 +90,6 @@ function initializePanzoom() { const startX = (viewportWidth - imageWidth * scale) / 2; const startY = (viewportHeight - imageHeight * scale) / 2; - console.log(startX, startY, imageWidth, imageHeight, viewportWidth, viewportHeight) - // But only modifying y is necessary because centering works VIA CESSPOOL HORIZONTALLY BUT NOT VERTICALLY panzoomInstance = Panzoom(popupImage, { startY: startY, diff --git a/config/_default/languages.en.toml b/config/_default/languages.en.toml index 50c557a..9e2aa68 100644 --- a/config/_default/languages.en.toml +++ b/config/_default/languages.en.toml @@ -35,6 +35,7 @@ title = "N.E.E.T. Works" #{ facebook = "https://facebook.com/username" }, #{ flickr = "https://www.flickr.com/photos/username/" }, #{ foursquare = "https://foursquare.com/username" }, + { gitea = "https://git.neet.works/rawhide_k" }, { github = "https://github.com/rawhide-kobayashi" }, #{ gitlab = "https://gitlab.com/username" }, #{ google = "https://www.google.com/" }, diff --git a/config/_default/params.toml b/config/_default/params.toml index 29874de..3574808 100644 --- a/config/_default/params.toml +++ b/config/_default/params.toml @@ -6,8 +6,8 @@ # https://blowfish.page/docs/configuration/#theme-parameters colorScheme = "patchouli" -defaultAppearance = "light" # valid options: light or dark -autoSwitchAppearance = true +defaultAppearance = "dark" # valid options: light or dark +autoSwitchAppearance = false enableSearch = true enableCodeCopy = true @@ -61,11 +61,11 @@ giteaDefaultServer = "https://git.neet.works" showDateOnlyInArticle = false showDateUpdated = true showAuthor = true - # showAuthorBottom = false + # showAuthorBottom = true showHero = true heroStyle = "background" # valid options: basic, big, background, thumbAndBackground - layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground - layoutBackgroundHeaderSpace = true # only used when heroStyle equals background + layoutBackgroundBlur = false # only used when heroStyle equals background or thumbAndBackground + layoutBackgroundHeaderSpace = false # only used when heroStyle equals background showBreadcrumbs = false showDraftLabel = true showEdit = false @@ -79,7 +79,7 @@ giteaDefaultServer = "https://git.neet.works" showTableOfContents = true showRelatedContent = true relatedContentLimit = 3 - showTaxonomies = false + showTaxonomies = true showAuthorsBadges = false showWordCount = true # sharingLinks = [ "linkedin", "twitter", "bluesky", "mastodon", "reddit", "pinterest", "facebook", "email", "whatsapp", "telegram"] diff --git a/content/posts/watercooling-my-homelab/images/compressed/featured.webp b/content/posts/watercooling-my-homelab/featured.webp similarity index 100% rename from content/posts/watercooling-my-homelab/images/compressed/featured.webp rename to content/posts/watercooling-my-homelab/featured.webp diff --git a/content/posts/watercooling-my-homelab/index.md b/content/posts/watercooling-my-homelab/index.md index eeb20c1..60bac08 100644 --- a/content/posts/watercooling-my-homelab/index.md +++ b/content/posts/watercooling-my-homelab/index.md @@ -19,7 +19,7 @@ categories: --- ## Overview -Watercooling - or, more accurately, custom loop watercooling[^badnomenclature] over AIOs - has increasingly transitioned to an aesthetic choice rather than a practical one in the consumer gaming space, with more energy efficient chips overclocked out the wazoo from the factory and relatively minimal gains to be made compared to cheaper and easier AIO solutions. However, there are still benefits to be had, marginal as they are, in performance, aesthetics, and convenience. A lot of people are intimidated by custom watercooling, especially when it comes to their costly (in terms of cash, or time, or both) homelab setups. Some will even call you CRAZY (which is very rude, by the way) for saying you want to watercool your server. In this post I'm going to showcase my solution for a leak-resistant watercooling system with monitoring that I trust to protect my beloved rack from the horrors of water damage as well as thermal throttling. +Watercooling - or, more accurately, custom loop watercooling[^badnomenclature], rather than AIOs - has increasingly transitioned to an aesthetic choice rather than a practical one in the consumer gaming space, with more energy efficient chips overclocked out the wazoo from the factory and relatively minimal gains to be made compared to cheaper and easier AIO solutions. However, there are still benefits to be had, marginal as they are, in performance, aesthetics, and longevity. In this post I'm going to showcase my solution for a leak-resistant watercooling system with monitoring that I trust to protect my beloved homelab from water damage as well as thermal throttling. {{< panzoom-figure src="images/compressed/Triple_Card_Jank.webp" @@ -27,7 +27,7 @@ Watercooling - or, more accurately, custom loop watercooling[^badnomenclature] o caption="My initial setup with 3x 2080 Tis, using m.2 NVMe to PCIe risers in an ASUS prebuilt. Two are connected by NVLINK, which I found to provide a slight performance benefit on the order of ~1-5% in multi-GPU SISR training, which is not worth the typical price for NVLINK bridges from this era. I was lucky to get this ugly, quadro-oriented bridge for just $40." >}} -I had wanted to watercool my ML setup for a while, particularly so I could use NVLINK without suffocating the GPUs. The above setup worked, but it was loud, the clock speeds were inconsistent, VRAM overclocking was very limited, they would bounce off the power/temperature limit, and it was an incredible pain to move whenever I had to poke around in there. However, I was very deeply concerned about the possibility of water damage. I've been watercooling since 2020, and I've never had a leak, but there were going to be more connected components than ever, with wider temperature swings, and collectively, a whole lot more expensive hardware that might get damaged than compared to my desktop setup. The situation became more dire to me, after I upgraded my main server and discovered that the forced-air passive chassis cooling was insufficient for my new CPUs. +I had wanted to watercool my ML setup for a while, particularly so I could use NVLINK without suffocating the GPUs. The above setup worked, but it was loud, the clock speeds were inconsistent, VRAM overclocking was very limited, they would bounce off the power/temperature limit, and it was an incredible pain to move whenever I had to poke around in there. Of course, I was concerned about the possibility of water damage. I've been doing custom watercooling since 2020 in my desktop, and I've never had a leak, but there were going to be more connected components than ever, with wider temperature swings, and collectively, a whole lot more expensive hardware that might get damaged than compared to my desktop setup. The situation became more dire to me, after I upgraded my main server and discovered that the forced-air passive chassis cooling was insufficient for my new CPUs. So, how do you make a fluid system resistant to leaks? Build it very well, with close attention to detail, tighten all the fittings very carefully, regularly replace your o-rings and leak-check extensively before operation? No! You pull a vacuum inside the fluid loop! Just think about it. Water can't get out if air is trying to get in. It's so simple. I wish I could say that I came up with the concept myself, but I didn't. After finding out that Aqua Computer has a product called LEAKSHIELD which does exactly that, I finally had the confidence to take the plunge on this project. @@ -65,7 +65,7 @@ My problematic upgrade was to dual Xeon Gold 6154s, which are Skylake-SP archite caption="Alphacool Eisblock XPX Pro coldplate Image credit & copyright - [igor'sLAB](https://www.igorslab.de/en/ryzen-threadripper-2990-wx-with-500-w-alphacool-iceblock-xpx-aurora-pro-plexi-digital-rgb-in-test/)" >}} -The CPU waterblocks are Alphacool Eisblock XPX Pro Aurora Light models, which are significantly cheaper than the XPX Aurora Pro not-light version. They appear to be entirely identical, functionally... I'm not sure if there any actual performance benefits offered by the not-light version. It's a relatively obscure block family without many thorough reviews, which makes sense, given this block is designed for full coverage on Xeons/Threadrippers. The coldplate appears to be skived, which is uncommon in this price bracket for a discrete block, and the fins are incredibly short and dense. A single instance of this block alone would restrict your loop's flow to a ridiculous degree, but in my case, having four blocks + quick disconnects makes it less overall impactful. At maximum load, the maximum core temperature delta relative to the water temperature is 25\*C, with a ~1-2\*C average delta between the two serially-connected sockets at a flow rate of ~130L/h, and that's more than sufficient. +The CPU waterblocks are Alphacool Eisblock XPX Pro Aurora Light models, which are significantly cheaper than the XPX Aurora Pro not-light version. They appear to be entirely identical, functionally... I'm not sure if there any actual performance benefits offered by the not-light version. It's a relatively obscure block family without many thorough reviews, which makes sense, given this block is designed for full coverage on Xeons/Threadrippers. The coldplate appears to be skived, which is uncommon in this price bracket for a discrete block, and the fins are incredibly short and dense. In smaller desktop loops, I've seen this block criticized for having overly restrictive flow, but when you have four blocks + quick disconnects, 'good' flow is relative. At maximum load, the maximum core temperature delta relative to the water temperature is 25\*C, with a ~1-2\*C average delta between the two serially-connected sockets at a flow rate of ~130L/h, and that's more than sufficient. {{< panzoom-figure src="images/compressed/Dual_Blocks_Zoom.webp" @@ -87,7 +87,7 @@ With the stock air cooler, I couldn't maintain 1800MHz core clock without altern The biggest benefit that watercooling brings to modern video cards is a prolonged lifespan. Not due to lower core temperatures, in an absolute sense, but due to the reduced stress from thermal cycles. Mismatches in the rate of thermal expansion between the die and the substrate will eventually cause their bond to break, and this happens faster the larger your die is. Today's GPU dies are huge, and this failure mode is the most common. -Thus, it follows, provided you don't somehow break things while installing the block, watercooling is the second-best method to ensure the longevity of your GPU behind never using it or always keeping it under full load. It also generally allows the memory to clock a bit higher as it can be kept significantly cooler by the less-heat-saturated surface area of the block compared to a traditional air cooler. Although I can't benchmark the before and after memory temperature on these cards in particular as they do not expose VRAM temperature sensors, I can confirm that putting them under water allowed the memory to clock marginally higher than under the stock air cooler. +Logically, provided you don't somehow break things while installing the block, watercooling is the second-best method to ensure the longevity of your GPU behind never using it, or always keeping it under full load. It also generally allows the memory to clock a bit higher as it can be kept significantly cooler by the less-heat-saturated surface area of the block compared to a traditional air cooler. Although I can't benchmark the before and after memory temperature on these cards in particular as they do not expose VRAM temperature sensors, I can confirm that putting them under water allowed the memory to clock marginally higher than under the stock air cooler. {{< panzoom-gallery caption="The GPU blocks required a *moderate amount of light massaging* to properly fit on these OEM model cards. The power plugs are in a different position and a singular capacitor on these models is slightly taller than on the actual Founder's Edition reference card, but they're otherwise identical. Enough.">}} {{< panzoom-figure @@ -145,7 +145,7 @@ Unfortunately, I didn't take excruciatingly detailed pictures of literally every Some time ago, my aunt gave me her first-gen Intel White iMac, which is visually very similar to the G5, and it was one of the earliest things that I installed Linux on. I used it as a seedbox for a bit, but eventually took it apart and saved some of the more interesting stuff. The hard drive is still running in my router today! {{< panzoom-figure - src="images/compressed/schematic_minify.svg" + src="schematic_minify.svg" alt="My schematic for the control unit. It's the first time I've used KiCad, and the first time I've ever made a schematic like this at all. I hope it's relatively legible." caption="My schematic for the control unit. It's the first time I've used KiCad, and the first time I've ever made a schematic like this at all. I hope it's relatively legible." >}} @@ -353,7 +353,7 @@ Theoretically the loop should handle water temperatures in excess of 60\*C witho Just a note, the software hadn't been 100% finalized when I took the below pictures. The control box does have a lid now, and all the cable management is a lot cleaner... Promise! -{{< panzoom-gallery caption="Required additions to the solenoid, pump motor, and the complete assembly without cover.">}} +{{< panzoom-gallery caption="Everything installed and working!">}} {{< panzoom-figure src="images/compressed/complete_a.webp" alt="Front-ish view of the complete rack assembly." diff --git a/layouts/_default/single.html b/layouts/_default/single.html new file mode 100644 index 0000000..87e4fbe --- /dev/null +++ b/layouts/_default/single.html @@ -0,0 +1,161 @@ +{{ define "main" }} +{{ .Scratch.Set "scope" "single" }} + +
+ {{ if .Params.showHero | default (.Site.Params.article.showHero | default false) }} + {{ $heroStyle := .Params.heroStyle }} + {{ if not $heroStyle }}{{ $heroStyle = .Site.Params.article.heroStyle }}{{ end }} + {{ $heroStyle := print "partials/hero/" $heroStyle ".html" }} + {{ if templates.Exists $heroStyle }} + {{ partial $heroStyle . }} + {{ else }} + {{ partial "partials/hero/basic.html" . }} + {{ end }} + {{ end }} + +
+ + {{ if or (and (.Params.showTableOfContents | default (.Site.Params.article.showTableOfContents | default false)) (in + .TableOfContents " +
+ + {{ if and (.Params.showTableOfContents | default (.Site.Params.article.showTableOfContents | default false)) (in + .TableOfContents " +
+ {{ end }} + +
+ +
+ {{ if .Params.showBreadcrumbs | default (.Site.Params.article.showBreadcrumbs | default false) }} + {{ partial "breadcrumbs.html" . }} + {{ end }} +

+ {{ .Title | emojify }} +

+
+ {{ partial "article-meta/basic.html" (dict "context" . "scope" "single") }} +
+ + {{ $authorsData := .Site.Data.authors }} + {{ $taxonomies := .Site.Taxonomies.authors }} + {{ $baseURL := .Site.BaseURL }} + {{ $taxonomyLink := 0 }} + {{ $showAuthor := 0 }} + + {{ if not (strings.HasSuffix $baseURL "/") }} + {{ $baseURL = delimit (slice $baseURL "/") "" }} + {{ end }} + + {{ if not (.Params.showAuthorBottom | default ( .Site.Params.article.showAuthorBottom | default false)) }} + + {{ if .Params.showAuthor | default (.Site.Params.article.showAuthor | default true) }} + {{ $showAuthor = 1 }} + {{ partial "author.html" . }} + {{ end }} + + {{ range $author := .Page.Params.authors }} + {{ $authorData := index $authorsData $author }} + {{- if $authorData -}} + {{ range $taxonomyname, $taxonomy := $taxonomies }} + {{ if (eq $taxonomyname $author) }} + {{ $taxonomyLink = delimit (slice $baseURL "authors/" $author "/") "" }} + {{ end }} + {{ end }} + {{ partial "author-extra.html" (dict "context" . "data" $authorData "link" $taxonomyLink) }} + {{- end -}} + {{ end }} + + {{ if or $taxonomyLink $showAuthor }} +
+ {{ end }} + + {{ end }} + +
+ + {{ partial "series/series.html" . }} + +
+ {{ .Content }} + {{ $defaultReplyByEmail := .Site.Params.replyByEmail }} + {{ $replyByEmail := default $defaultReplyByEmail .Params.replyByEmail }} + {{ if $replyByEmail }} + + + Reply by Email + + + {{ end }} +
+ + {{ if (.Params.showAuthorBottom | default ( .Site.Params.article.showAuthorBottom | default false)) }} + + {{ if .Params.showAuthor | default (.Site.Params.article.showAuthor | default true) }} + {{ $showAuthor = 1 }} + {{ partial "author.html" . }} + {{ end }} + + {{ range $author := .Page.Params.authors }} + {{ $authorData := index $authorsData $author }} + {{- if $authorData -}} + {{ range $taxonomyname, $taxonomy := $taxonomies }} + {{ if (eq $taxonomyname $author) }} + {{ $taxonomyLink = delimit (slice $baseURL "authors/" $author "/") "" }} + {{ end }} + {{ end }} + {{ partial "author-extra.html" (dict "context" . "data" $authorData "link" $taxonomyLink) }} + {{- end -}} + {{ end }} + + {{ if or $taxonomyLink $showAuthor }} +
+ {{ end }} + + {{ end }} + + {{ partial "series/series-closed.html" . }} + {{ partial "sharing-links.html" . }} + {{ partial "related.html" . }} +
+ + {{ $translations := .AllTranslations }} + {{ with .File }} + {{ $path := .Path }} + {{range $translations}} + {{ $lang := print "." .Lang ".md" }} + {{ $path = replace $path $lang ".md" }} + {{end}} + + {{ $jsPage := resources.Get "js/page.js" }} + {{ $jsPage = $jsPage | resources.Minify | resources.Fingerprint "sha512" }} + + {{ end }} + +
+ +
+{{ end }} diff --git a/resources/_gen/images/posts/watercooling-my-homelab/featured_hu11504899310106128621.webp b/resources/_gen/images/posts/watercooling-my-homelab/featured_hu11504899310106128621.webp new file mode 100644 index 0000000..c367236 Binary files /dev/null and b/resources/_gen/images/posts/watercooling-my-homelab/featured_hu11504899310106128621.webp differ diff --git a/resources/_gen/images/posts/watercooling-my-homelab/featured_hu13568191302889737537.webp b/resources/_gen/images/posts/watercooling-my-homelab/featured_hu13568191302889737537.webp new file mode 100644 index 0000000..87b93fe Binary files /dev/null and b/resources/_gen/images/posts/watercooling-my-homelab/featured_hu13568191302889737537.webp differ