backup styling work before potential major oopsie

This commit is contained in:
rawhide kobayashi 2025-01-08 00:50:33 -06:00
parent 5f436bd2b8
commit 56c233a5f4
Signed by: rawhide_k
GPG Key ID: E71F77DDBC513FD7
9 changed files with 304 additions and 17 deletions

View File

@ -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 */
}
.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);
}

View File

@ -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,

View File

@ -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/" },

View File

@ -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"]

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -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."

View File

@ -0,0 +1,161 @@
{{ define "main" }}
{{ .Scratch.Set "scope" "single" }}
<article>
{{ 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 }}
<section class="flex flex-col max-w-full mt-0 prose dark:prose-invert lg:flex-row">
{{ if or (and (.Params.showTableOfContents | default (.Site.Params.article.showTableOfContents | default false)) (in
.TableOfContents "<ul")) (.Site.Params.article.showRelatedPosts | default false) }} <div
class="order-first lg:ml-auto px-0 lg:order-last ltr:lg:pl-8 rtl:lg:pr-8">
<div class="toc ltr:pl-5 rtl:pr-5 print:hidden lg:sticky {{ if hasPrefix .Site.Params.header.layout "fixed" -}}
lg:top-[140px]{{ else }}lg:top-10{{ end }}">
{{ if and (.Params.showTableOfContents | default (.Site.Params.article.showTableOfContents | default false)) (in
.TableOfContents "<ul") }} {{ partial "toc.html" . }} {{ end }} {{ if .Site.Params.article.showRelatedPosts |
default false }} sd {{ end }} </div>
</div>
{{ end }}
<div class="min-w-0 min-h-0 max-w-fit">
<header id="single_header" class="mt-5 max-w-prose">
{{ if .Params.showBreadcrumbs | default (.Site.Params.article.showBreadcrumbs | default false) }}
{{ partial "breadcrumbs.html" . }}
{{ end }}
<h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral">
{{ .Title | emojify }}
</h1>
<div class="mt-1 mb-6 text-base text-neutral-500 dark:text-neutral-400 print:hidden">
{{ partial "article-meta/basic.html" (dict "context" . "scope" "single") }}
</div>
{{ $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 }}
<div class="mb-5"></div>
{{ end }}
{{ end }}
</header>
{{ partial "series/series.html" . }}
<div class="article-content max-w-prose mb-20">
{{ .Content }}
{{ $defaultReplyByEmail := .Site.Params.replyByEmail }}
{{ $replyByEmail := default $defaultReplyByEmail .Params.replyByEmail }}
{{ if $replyByEmail }}
<strong class="block mt-8">
<a target="_blank"
class="m-1 rounded bg-neutral-300 p-1.5 text-neutral-700 hover:bg-primary-500 hover:text-neutral dark:bg-neutral-700 dark:text-neutral-300 dark:hover:bg-primary-400 dark:hover:text-neutral-800"
href="mailto:{{ .Site.Params.Author.email }}?subject={{ replace (printf "Reply to %s" .Title) "\"" "'" }}">
Reply by Email
</a>
</strong>
{{ end }}
</div>
{{ 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 }}
<div class="mb-10"></div>
{{ end }}
{{ end }}
{{ partial "series/series-closed.html" . }}
{{ partial "sharing-links.html" . }}
{{ partial "related.html" . }}
</div>
{{ $translations := .AllTranslations }}
{{ with .File }}
{{ $path := .Path }}
{{range $translations}}
{{ $lang := print "." .Lang ".md" }}
{{ $path = replace $path $lang ".md" }}
{{end}}
<script>
var oid = "views_{{ $path }}"
var oid_likes = "likes_{{ $path }}"
</script>
{{ $jsPage := resources.Get "js/page.js" }}
{{ $jsPage = $jsPage | resources.Minify | resources.Fingerprint "sha512" }}
<script type="text/javascript" src="{{ $jsPage.RelPermalink }}" integrity="{{ $jsPage.Data.Integrity }}"></script>
{{ end }}
</section>
<footer class="pt-8 max-w-prose print:hidden">
{{ partial "article-pagination.html" . }}
{{ if .Params.showComments | default (.Site.Params.article.showComments | default false) }}
{{ if templates.Exists "partials/comments.html" }}
<div class="pt-3">
<hr class="border-dotted border-neutral-300 dark:border-neutral-600" />
<div class="pt-3">
{{ partial "comments.html" . }}
</div>
</div>
{{ else }}
{{ warnf "[BLOWFISH] Comments are enabled for %s but no comments partial exists." .File.Path }}
{{ end }}
{{ end }}
</footer>
</article>
{{ end }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB