This commit is contained in:
Snorre Ettrup Altschul 2025-02-06 17:03:01 +01:00
parent 23a51562a1
commit b036acd3f4
7 changed files with 188 additions and 31 deletions

View file

@ -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);

View file

@ -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>">&leftarrow; prev</a> <a href="https://nixwebr.ing">nixwebr.ing</a> -->
<!-- <a href="https://nixwebr.ing/prev/<name>">next &rightarrow;</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>

View file

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

View file

@ -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.
![Image of a server displaying the startup message](/posts/minecraft-proxy/starting.png "A server that is waiting to be started")
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.
![Hibernating server](/posts/minecraft-proxy/hibernating.png "Hibernating server")
# 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.

View file

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

View file

@ -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**
![test broken image](broken_image.png)
![test image](/test.png)
![test image](/test.png "Image with caption")

5
public/posts/toc.md Normal file
View file

@ -0,0 +1,5 @@
0
Tick Tock Cock
# Table of Contents test
\toc