From b036acd3f4ea62a6b31ed24d2566890594788fc6 Mon Sep 17 00:00:00 2001 From: Snorre Ettrup Altschul <snorre@altschul.dk> Date: Thu, 6 Feb 2025 17:03:01 +0100 Subject: [PATCH] toc wip --- public/css/css.css | 4 + public/index.html | 29 +++++-- public/js/md2html.js | 85 ++++++++++++++++--- ...tting hungry minecraft servers to sleep.md | 67 +++++++++++---- public/posts/index.html | 18 ++++ public/posts/test.md | 11 +++ public/posts/toc.md | 5 ++ 7 files changed, 188 insertions(+), 31 deletions(-) create mode 100644 public/posts/toc.md diff --git a/public/css/css.css b/public/css/css.css index 7aee9e4..a05d6af 100644 --- a/public/css/css.css +++ b/public/css/css.css @@ -8,6 +8,10 @@ code { border-radius: var(--radius); } +ul.posts>li { + margin-top: var(--margin); +} + pre.codeblock { background: var(--background-text); color: var(--text); diff --git a/public/index.html b/public/index.html index 2deb6b8..3ec0d87 100644 --- a/public/index.html +++ b/public/index.html @@ -20,11 +20,12 @@ <li>Vim motions</li> </ul> </div> - <div class="content" style="grid-area: content2"> - <a href="/posts"><h2>Projects I've finished</h2></a> + <div class="content posts" style="grid-area: content2"> + <h2><a href="posts">Projects I've finished</a></h2> <ul> - <li><a href="/posts?post=openbirch">Openbirch</a><br>(made it to an alpha release)</li> - <li><a href="/posts?post=minecraft-proxy">Minecraft server + <li><a href="/posts?post=How I made a programming language 700x slower than python">Openbirch</a><br>(made it to + an alpha release)</li> + <li><a href="/posts?post=Putting hungry minecraft servers to sleep">Minecraft server hibernator</a></li> <li><a href="https://gitlab.com/SpoodyTheOne/the-wheel">Wheel of names drinking game</a><br>(scraping the bottom of the barrel for finished projects)</li> @@ -32,7 +33,9 @@ </div> <!-- <div class="content" style="grid-area: content3">content</div> --> - <div class="footer" style="grid-area: footer1"><marquee>Footer</marquee></div> + <div class="footer" style="grid-area: footer1"> + <marquee>Footer</marquee> + </div> <div class="footer" style="grid-area: footer2; text-align: center"> <!-- <a href="https://nixwebr.ing/next/<name>">← prev</a> <a href="https://nixwebr.ing">nixwebr.ing</a> --> <!-- <a href="https://nixwebr.ing/prev/<name>">next →</a> --> @@ -49,6 +52,22 @@ <script src="/js/main.js"></script> <script> generate_background(); + + (async () => { + let response = await fetch(`/posts/index`); + if (response.status != 200) + return; + + let posts = (await response.text()).split("\n"); + + let p = document.querySelector("#content>.content.posts>h2"); + p.innerText = "Recent posts"; + + let elem = document.querySelector("#content>.content.posts>ul"); + elem.innerHTML = posts.slice(0,3).map(x => `<li><a href="/posts?post=${x}">${x}</a></li>`).join("\n"); + elem.style.maxWidth = "40ch"; + elem.classList.add("posts"); + })() </script> </body> diff --git a/public/js/md2html.js b/public/js/md2html.js index dad335e..aa98e10 100644 --- a/public/js/md2html.js +++ b/public/js/md2html.js @@ -61,14 +61,23 @@ const md2tokens = (markdown, newline) => { switch (markdown[i]) { case '\\': i++; + + if (peek(0) == 't' && peek(1) == 'o' && peek(2) == 'c') { + advance(); + advance(); + advance(); + tokens.push({ type: "toc" }); + break; + } + current_string += markdown[i]; break; case '*': finish(); advance(); - newline=false; - newline_count=0; + newline = false; + newline_count = 0; let depth_em = 1; let type = "em"; if (match("*")) { @@ -82,8 +91,8 @@ const md2tokens = (markdown, newline) => { } current_string = capture_until_predicate((i) => { let found = true; for (let j = 0; j < depth_em; j++) { if (markdown[i + j] != "*") { found = false; break; } } return found; }) - - for (let j=0;j<depth_em-1;j++) { + + for (let j = 0; j < depth_em - 1; j++) { // console.log(markdown[i] == "\n"); advance(); } @@ -103,6 +112,8 @@ const md2tokens = (markdown, newline) => { depth_he++; } while (match("#")); + advance(); + while (markdown[i] != "\n" && markdown.length > i) { current_string += markdown[i]; i++ @@ -110,6 +121,7 @@ const md2tokens = (markdown, newline) => { tokens.push({ type: "header", level: depth_he, content: current_string }); current_string = ""; + newline_count = 2; break; case '!': @@ -144,23 +156,37 @@ const md2tokens = (markdown, newline) => { tokens.push({ type: "image", alt: alt_text, url: url, title: title }) break; + case '>': case '-': if (!newline) { def(); break; } + if (peek(0) == "-" && peek(1) == "-" && peek(2) == "-" && peek(3) == "\n") { + advance(); + advance(); + advance(); + + tokens.push({ type: "hline" }); + break; + } + + let type_l = peek(0) == '-' ? "ul" : "quote"; + let ch = peek(0); + let items = []; advance(); do { + match(" "); let text = capture_until("\n"); advance(); items.push(md2tokens(text, false)); - } while (match("-")); + } while (match(ch)); - tokens.push({ type: "ul", items: items }); + tokens.push({ type: type_l, items: items }); break; @@ -177,8 +203,8 @@ const md2tokens = (markdown, newline) => { case '[': finish(); advance(); - newline=false; - newline_count=0; + newline = false; + newline_count = 0; let text = capture_until("]"); advance(); @@ -196,8 +222,8 @@ const md2tokens = (markdown, newline) => { case '`': finish(); advance(); - newline=false; - newline_count=0; + newline = false; + newline_count = 0; let stop = (i) => markdown[i] == "`"; let type_c = "inlinecode"; let language = ""; @@ -211,7 +237,7 @@ const md2tokens = (markdown, newline) => { current_string = capture_until_predicate(stop); tokens.push({ type: type_c, content: current_string, language: language }); current_string = ""; - if (type_c == "codeblock") { advance(); advance(); newline_count=2; } // remove trailing ` + if (type_c == "codeblock") { advance(); advance(); newline_count = 2; } // remove trailing ` break; default: @@ -240,6 +266,32 @@ const tokens2html = (tokens) => { for (let token of tokens) { switch (token.type) { + case "toc": + let htok = tokens.filter(x => x.type == "header"); + console.log(htok); + const createList = (tokens, depth = 2) => { + let list = ''; + let listItems = ''; + + + while (tokens.length > 0 && tokens[0].level <= depth) { + const token = tokens.shift(); + listItems += `<li><a href="#${token.content}">${token.content}</a></li>`; + + if (tokens.length > 0 && tokens[0].level > token.depth) { + listItems += createList(tokens, token.depth + 1); + } + } + + if (listItems) { + list = `<ol>${listItems}</ol>`; + } + + return list; + }; + + output += createList(htok); + break; case "span": output += `<span>${token.content}</span>`; break @@ -254,6 +306,10 @@ const tokens2html = (tokens) => { break; case "header": output += `<h${token.level}>${token.content}</h${token.level}>` + if (token.level == 2) output += "<hr>"; + break; + case "hline": + output += "<hr>"; break; case "newline": output += "<br>" @@ -269,6 +325,13 @@ const tokens2html = (tokens) => { } output += "</ul>"; break; + case "quote": + output += "<pre class=\"quote\">\n"; + for (let item of token.items) { + output += `${tokens2html(item)}\n`; + } + output += "</pre>"; + break; case "inlinecode": output += `<code>${token.content}</code>`; break; diff --git a/public/posts/Putting hungry minecraft servers to sleep.md b/public/posts/Putting hungry minecraft servers to sleep.md index 1e7bc74..894bd7b 100644 --- a/public/posts/Putting hungry minecraft servers to sleep.md +++ b/public/posts/Putting hungry minecraft servers to sleep.md @@ -1,34 +1,71 @@ 2 Minecraft Rust Async Networking Proxy CRIU # Minecraft servers are HUNGRY + They hunger for your ram and your cpu. This makes it either expensive or laggy to try and host multiple servers at once. This was something I encountered when my friend built a homeserver out of spare computer parts that was barely powerful enough to run a minecraft server. -The problem was that soon multiple people wanted a minecraft server hosted by him, including ourselves with modded experiences. +The problem was that soon multiple people wanted a minecraft server hosted by him (which included us wanting to play modded minecraft). It was a hassle to ssh into the server and start and stop the various servers depending on who wanted to play, especially since a lot of people only played very rarily. I remembered that I'd seen [a project](https://github.com/gekware/minecraft-server-hibernation) that claimed to be able to hibernate a minecraft server if nobody was playing on it. -This worked by starting and stopping the server, leading to noticeable join delays when joining modded servers. +The only issue was that it worked for a single server, and did so by starting and stopping the server process. -Another nice-to-have was being able to join through a subdomain. After having messed around with nginx to help my friend set up various services like nextcloud -on subdomains we wanted to do the same for the minecraft servers. +This meant that if we wanted to join a modded server the hundreds of mods could make us wait for several minutes before we could play. -Being able to join the vanilla server through `vanilla.domain.com` and the modded through `modded.domain.com` would simply be really cool. +Another issue was the fact that it could only host a single server. This meant that we would have to run multiple intances of the watcher, +and that each server would be assigned to an arbitrary port that would be needed when connecting. -# Minecraft Server Hibernation Proxy Manager Rs (Name WIP) +# Building a reverse proxy for minecraft -I ended up writing [a Rust program](https://gitlab.com/SpoodyTheOne/minecraft-server-hibernation-proxy-rust) that could do this and more. It listens on the default minecraft server port of `25565` and parses any incoming data as minecraft packets. -If the data contains a url that matches `*.domain.com` then it uses the subdomain to look up a configured server, and if found redirects all future traffic to that port. +Since my friends server was accessible through a domain we thought it would be cool if instead of supplying a port +you could connect to a subdomain and be sent to a specific server. - +The simplest way to do this would be to create a dhcp record for each subdomain to point to a server, +but that would be slow and tedious to set up for every server. -If the server is stopped or hibernating then the command to start it is automatically run. Likewise if all players leave a configurable countdown is started. -Once it reaches 0 the server is hibenated using CRIU, which writes the entire memory and all open file descriptors of the server program to disk. +We then tried nginx, as it seemingly could magically redirect traffic to an internal port based on the subdomain. +I quickly found out that this did *not* work for minecraft servers (who would have guessed), but after doing some research +I decided on creating my own reverse proxy that spoke the minecraft protocol instead of HTTP. - +# The Minecraft protocol -This means that joining a hibernated server only takes a few seconds, and any server currently hibernating will only take up disk space. -It can also be restored even after system restarts, so it would be possible to halt systemd shutdowns until all servers are hibernated. +Minecraft implements its own protocol consistent of packets. My first idea was to see if anybody had created a +rust library for dealing with minecraft packets. -<marquee style="font-size: 20em">Marq**WEE**</marquee> +While some did exist, most of them where unfinished or couldn't do what I wanted. [One crate](https://www.youtube.com/watch?v=E4WlUXrJgy4) +was useful for seeing how an implementation of parsing packets was done, but ultimately I had to write my own parser. + +*images of minecraft protocol* + +# Detecting the subdomain + +Luckily for me, the Minecraft procotol sends the full address it is trying to connect to during the handshake. This meant that I simply +had to parse the handshake and extract the subdomain from the address, see if it matches any configured server, and then redirect all +traffic to the internal port that server runs on. + +*code explanation* + +# Why aren't my chunks loading? + +Just as I thought I was finally done I encountered a problem. The chunks where loading *really* slowly. It felt like we we're back to +hosting on [aternos](aternos.org) (nothing against aternos, the **free** servers are just a little slow). + +*image of chunks not loading* + +I couldn't figure out why it was so slow. I thought it was because all the traffic had to flow through my program, +but the sockets are connected on the kernel level. + +After hours of debugging I found the solution. `stream.set_nodelay(true)` + +## ... + +**All this time the problem was as simple as saying ** `if (slow) { dont(); }`! + +To say I was pissed was an understatement, but at the same time I was glad it was finally working. + +The reason this fixed it was because it disabled [Nagle's algorithm](https://en.wikipedia.org/wiki/Nagle%27s_algorithm), which bunches packets together +in order to (hopefully) speed up the sending of lots of smaller packets as one big packet. + +For some reason this absolutely destroyed the chunk loading speed, and disabling it led to finally having the perfect Minecraft reverse proxy hibernation system thing. diff --git a/public/posts/index.html b/public/posts/index.html index 31fe201..808844c 100644 --- a/public/posts/index.html +++ b/public/posts/index.html @@ -21,6 +21,24 @@ ul,ol { margin-top:0; + padding-left: calc(4*var(--margin)); + } + + pre.quote { + font: inherit; + + background: var(--background-text); + border-left: var(--margin) solid var(--border); + border-radius: 0 var(--radius) var(--radius) 0; + /* padding-bottom: var(--margin); */ + padding: var(--padding); + } + + hr { + border: none; + background-color: var(--border); + border-radius: var(--radius); + height: 2px; } img { diff --git a/public/posts/test.md b/public/posts/test.md index 02f5bd7..a6af185 100644 --- a/public/posts/test.md +++ b/public/posts/test.md @@ -1,5 +1,6 @@ 0 Test +\toc # H1 ## H2 ### H3 @@ -16,6 +17,8 @@ This is regular text ***bold italic*** +--- + `this is an inline code block` ``` @@ -60,6 +63,14 @@ in - Test - here +> this +> is +> a +> +> QUOTE + +> **QUOTE** +    diff --git a/public/posts/toc.md b/public/posts/toc.md new file mode 100644 index 0000000..b1a302a --- /dev/null +++ b/public/posts/toc.md @@ -0,0 +1,5 @@ +0 +Tick Tock Cock +# Table of Contents test + +\toc