did some grinding off camera
|
@ -1,4 +1,4 @@
|
|||
@import url(https://fonts.bunny.net/css?family=chivo-mono:500);
|
||||
@import url(https://fonts.bunny.net/css?family=albert-sans:400,800);
|
||||
@import url(/css/colours.css);
|
||||
|
||||
code {
|
||||
|
@ -8,6 +8,34 @@ code {
|
|||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
h1 {
|
||||
letter-spacing: -4%;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
letter-spacing: -3%;
|
||||
margin: calc(2*var(--margin)) 0
|
||||
}
|
||||
|
||||
h1,h2,h3 {
|
||||
margin-bottom:0px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: calc(4*var(--margin));
|
||||
}
|
||||
|
||||
li::marker {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
|
||||
ul.posts>li {
|
||||
margin-top: var(--margin);
|
||||
}
|
||||
|
@ -23,7 +51,7 @@ pre.codeblock {
|
|||
html {
|
||||
color: var(--text);
|
||||
background-color: var(--background);
|
||||
font-family: 'Chivo Mono', monospace;
|
||||
font-family: 'Albert Sans', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -33,9 +61,15 @@ body {
|
|||
|
||||
a {
|
||||
color: var(--link);
|
||||
transition: 0.1s ease-in-out;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--link-visited);
|
||||
}
|
||||
|
||||
|
@ -49,7 +83,7 @@ body {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
line-height: 1.5;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
#content {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/css.css">
|
||||
</head>
|
||||
|
||||
|
@ -37,15 +38,13 @@
|
|||
<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/next/<name>">← prev</a> <a href="https://nixwebr.ing">Nix</a> -->
|
||||
<!-- <a href="https://nixwebr.ing/prev/<name>">next →</a> -->
|
||||
webbringGg
|
||||
</div>
|
||||
<div class="footer" style="grid-area: footer3; text-align: center;">
|
||||
<!-- <a href="https://512kb.club"><img style="aspect-ratio: 234.383/30" height=30 -->
|
||||
<!-- <a href="https://512kb.club" style="padding:0"><img style="aspect-ratio: 234.383/30" height=24 -->
|
||||
<!-- alt="a proud member of the green team of 512KB club" -->
|
||||
<!-- src="https://512kb.club/assets/images/green-team.svg"></a> -->
|
||||
aspirant of 512kb.club
|
||||
</div>
|
||||
</div>
|
||||
<div id="background"></div>
|
||||
|
@ -61,10 +60,10 @@
|
|||
let posts = (await response.text()).split("\n");
|
||||
|
||||
let p = document.querySelector("#content>.content.posts>h2");
|
||||
p.innerText = "Recent posts";
|
||||
p.innerHTML = "Recent <a href=\"/posts\">posts</a>";
|
||||
|
||||
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.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");
|
||||
})()
|
||||
|
|
|
@ -23,6 +23,16 @@ const match = (m) => (peek, take) => {
|
|||
take(m.length);
|
||||
return true;
|
||||
}
|
||||
const match_identifier = (m) => (peek, take) => {
|
||||
if (match(m)(peek, take)) {
|
||||
if (peek(0).match(/[0-9a-z]/)) {
|
||||
take(-m.length);
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const take_until = (p) => (peek, take) => {
|
||||
let b = "";
|
||||
while (!p()) {
|
||||
|
@ -33,7 +43,7 @@ const take_until = (p) => (peek, take) => {
|
|||
}
|
||||
|
||||
const simpleHighlight = (c) => (keyword) => (peek, take) => {
|
||||
if (match(keyword)(peek, take))
|
||||
if (match_identifier(keyword)(peek, take))
|
||||
return `<span class="${c}">${keyword}</span>`;
|
||||
}
|
||||
|
||||
|
@ -60,7 +70,7 @@ const multiline_comment = (prefix, suffix) => (peek, take) => {
|
|||
const string = (c) => (peek, take) => {
|
||||
if (match(c)(peek, take)) {
|
||||
let b = c;
|
||||
b += take_until(() => match(c)(peek,take))(peek, take);
|
||||
b += take_until(() => match(c)(peek, take))(peek, take);
|
||||
b += c;
|
||||
return `<span class="string">${b}</span>`
|
||||
}
|
||||
|
|
|
@ -62,13 +62,23 @@ const md2tokens = (markdown, newline) => {
|
|||
case '\\':
|
||||
i++;
|
||||
|
||||
if (peek(0) == 't' && peek(1) == 'o' && peek(2) == 'c') {
|
||||
advance();
|
||||
advance();
|
||||
advance();
|
||||
const match_word = (word) => {
|
||||
for (let j = 0; j < word.length; j++) {
|
||||
if (peek(j) != word[j])
|
||||
return false;
|
||||
}
|
||||
i += word.length;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (match_word("toc")) {
|
||||
tokens.push({ type: "toc" });
|
||||
break;
|
||||
}
|
||||
if (match_word("title")) {
|
||||
tokens.push({ type: "title" });
|
||||
break;
|
||||
}
|
||||
|
||||
current_string += markdown[i];
|
||||
break;
|
||||
|
@ -131,8 +141,7 @@ const md2tokens = (markdown, newline) => {
|
|||
}
|
||||
|
||||
finish();
|
||||
advance();
|
||||
advance();
|
||||
i += 2;
|
||||
let alt_text = capture_until("]");
|
||||
advance();
|
||||
if (!match("(")) {
|
||||
|
@ -164,10 +173,7 @@ const md2tokens = (markdown, newline) => {
|
|||
}
|
||||
|
||||
if (peek(0) == "-" && peek(1) == "-" && peek(2) == "-" && peek(3) == "\n") {
|
||||
advance();
|
||||
advance();
|
||||
advance();
|
||||
|
||||
i += 3;
|
||||
tokens.push({ type: "hline" });
|
||||
break;
|
||||
}
|
||||
|
@ -191,6 +197,15 @@ const md2tokens = (markdown, newline) => {
|
|||
break;
|
||||
|
||||
case '\n':
|
||||
if ((peek(-1) != " " || peek(-2) != " ") && peek(-1) != "\n") {
|
||||
current_string += " ";
|
||||
tokens.push({ type: "span", content: current_string });
|
||||
current_string = "";
|
||||
newline = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (newline_count > 2)
|
||||
break;
|
||||
|
||||
|
@ -198,6 +213,8 @@ const md2tokens = (markdown, newline) => {
|
|||
newline_count++;
|
||||
finish();
|
||||
tokens.push({ type: "newline" })
|
||||
if (newline_count == 1 && peek(-1) == "\n")
|
||||
tokens.push({ type: "newline" })
|
||||
break;
|
||||
|
||||
case '[':
|
||||
|
@ -261,40 +278,20 @@ const highlight_code = (language, code) => {
|
|||
return highlight(map, code);
|
||||
}
|
||||
|
||||
const tokens2html = (tokens) => {
|
||||
const tokens2html = (tokens, title) => {
|
||||
let output = "";
|
||||
|
||||
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);
|
||||
output += "TABLE LE CONTENTOS"
|
||||
break;
|
||||
case "title":
|
||||
output += `<h1>${title}</h1>`
|
||||
break;
|
||||
case "span":
|
||||
output += `<span>${token.content}</span>`;
|
||||
break
|
||||
break;
|
||||
case "bold":
|
||||
output += `<b>${token.content}</b>`
|
||||
break;
|
||||
|
@ -305,7 +302,7 @@ const tokens2html = (tokens) => {
|
|||
output += `<b><i>${token.content}</i></b>`
|
||||
break;
|
||||
case "header":
|
||||
output += `<h${token.level}>${token.content}</h${token.level}>`
|
||||
output += `<h${token.level} id="${token.content.replace(/ /g, "-")}">${token.content}</h${token.level}>`
|
||||
if (token.level == 2) output += "<hr>";
|
||||
break;
|
||||
case "hline":
|
||||
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
@ -2,32 +2,49 @@
|
|||
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 (which included us wanting to play modded minecraft).
|
||||
They hunger for your ram and your cpu. This makes it either expensive or
|
||||
laggy to try and host multiple servers at once.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
The only issue was that it worked for a single server, and did so by starting and stopping the server process.
|
||||
The problem was that soon multiple people wanted a minecraft server
|
||||
hosted by him (which included us wanting to play modded minecraft).
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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. The only issue was that it worked for a single server, and
|
||||
did so by starting and stopping the server process.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
# Building a reverse proxy for minecraft
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 27 KiB |
|
@ -2,6 +2,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/css/css.css">
|
||||
<style>
|
||||
#content {
|
||||
|
@ -11,7 +12,7 @@
|
|||
"footer2 footer"
|
||||
;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
max-width: 120ch;
|
||||
max-width: 75ch;
|
||||
}
|
||||
|
||||
#content>.content {
|
||||
|
@ -19,11 +20,6 @@
|
|||
text-justify: auto;
|
||||
}
|
||||
|
||||
ul,ol {
|
||||
margin-top:0;
|
||||
padding-left: calc(4*var(--margin));
|
||||
}
|
||||
|
||||
pre.quote {
|
||||
font: inherit;
|
||||
|
||||
|
@ -43,8 +39,9 @@
|
|||
|
||||
img {
|
||||
background-color: var(--background-text);
|
||||
|
||||
border-radius: var(--radius);
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img.enlarged {
|
||||
|
@ -75,7 +72,7 @@
|
|||
<body style="background-color: #222;">
|
||||
<div id="content">
|
||||
<div class="header" style="grid-area: header; text-align: center">
|
||||
<a href="/posts" style="color: inherit; text-decoration: inherit">
|
||||
<a href="/" style="color: inherit; text-decoration: inherit">
|
||||
<h1>SpoodyThe<span style="color:var(--background-text)">.</span>One</h1>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -102,7 +99,7 @@
|
|||
generate_background(posts);
|
||||
|
||||
let elem = document.querySelector("#content>.content");
|
||||
elem.innerHTML = posts.map(x => `<a href="/posts?post=${x}"><h2>${x}</h2></a>`).join("<br>\n");
|
||||
elem.innerHTML = `<ul>${posts.map(x => `<li><a href="/posts?post=${x}" style="font-size: 1.2em;">${x}</a></li>`).join("\n")}</ul>`;
|
||||
elem.style.maxWidth = "80ch";
|
||||
document.querySelector(".header>a").href = "/";
|
||||
};
|
||||
|
@ -116,12 +113,17 @@
|
|||
return;
|
||||
}
|
||||
|
||||
let response = await fetch(`/posts/${post}.md`);
|
||||
let content = await response.text();
|
||||
let response = await fetch(`/posts/${post}/post.md`);
|
||||
let content = ""
|
||||
|
||||
if (response.status != 200) {
|
||||
content = `\nError Exception Null\n#Unknown post '${post}'`
|
||||
}
|
||||
response = await fetch(`/posts/${post}.md`);
|
||||
if (response.status != 200) {
|
||||
content = `\nError Exception Null\n# Unknown post '${post}'`
|
||||
} else
|
||||
content = await response.text();
|
||||
} else
|
||||
content = await response.text();
|
||||
|
||||
let lines = content.split("\n");
|
||||
lines.shift();
|
||||
|
@ -130,7 +132,7 @@
|
|||
|
||||
generate_background(terms.split(" "));
|
||||
|
||||
document.getElementsByClassName("content")[0].innerHTML = tokens2html(md2tokens(content));
|
||||
document.getElementsByClassName("content")[0].innerHTML = tokens2html(md2tokens(content), post);
|
||||
|
||||
await new Promise(res => setTimeout(res, 5000));
|
||||
if (document.body.scrollHeight > window.innerHeight) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
0
|
||||
Test
|
||||
\title
|
||||
---
|
||||
\toc
|
||||
# H1
|
||||
## H2
|
||||
|
@ -31,6 +33,7 @@ this is a code block
|
|||
*/
|
||||
void main(int argc, char** argv) {
|
||||
std::string this = "c++ code block";
|
||||
int integer = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -46,7 +49,7 @@ private:
|
|||
```
|
||||
|
||||
```nix
|
||||
{ ... }:
|
||||
{ ... } @ inputs :
|
||||
let
|
||||
this = "a nix code block";
|
||||
in
|
||||
|
|
38
src/main.rs
|
@ -60,9 +60,13 @@ impl Request {
|
|||
path.push("public");
|
||||
|
||||
path.push(
|
||||
PathBuf::from(urlencoding::decode(self.path.as_ref().unwrap()).unwrap().to_string())
|
||||
.strip_prefix("/")
|
||||
.expect("Failed to strip prefix"),
|
||||
PathBuf::from(
|
||||
urlencoding::decode(self.path.as_ref().unwrap())
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
)
|
||||
.strip_prefix("/")
|
||||
.expect("Failed to strip prefix"),
|
||||
);
|
||||
|
||||
if path.extension() == None {
|
||||
|
@ -119,13 +123,13 @@ fn handle_connection(mut stream: TcpStream) {
|
|||
let file_name = x.file_name();
|
||||
let y = file_name.to_str();
|
||||
if y.is_some() {
|
||||
if y.unwrap().ends_with(".md") {
|
||||
true
|
||||
} else {
|
||||
if y.unwrap().ends_with(".html") {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect::<Vec<DirEntry>>();
|
||||
|
@ -133,12 +137,24 @@ fn handle_connection(mut stream: TcpStream) {
|
|||
let mut a = if let Ok(a) = std::fs::File::open(a.path()) {
|
||||
a
|
||||
} else {
|
||||
return std::cmp::Ordering::Equal;
|
||||
let mut path = a.path().clone();
|
||||
path.push("post.md");
|
||||
if let Ok(a) = std::fs::File::open(path) {
|
||||
a
|
||||
} else {
|
||||
return std::cmp::Ordering::Equal;
|
||||
}
|
||||
};
|
||||
let mut b = if let Ok(b) = std::fs::File::open(b.path()) {
|
||||
b
|
||||
} else {
|
||||
return std::cmp::Ordering::Equal;
|
||||
let mut path = b.path().clone();
|
||||
path.push("post.md");
|
||||
if let Ok(b) = std::fs::File::open(path) {
|
||||
b
|
||||
} else {
|
||||
return std::cmp::Ordering::Equal;
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf: &mut [u8] = &mut [0; 16];
|
||||
|
@ -179,9 +195,9 @@ fn handle_connection(mut stream: TcpStream) {
|
|||
.filter_map(|x| {
|
||||
if let Some(n) = x.file_name().to_str() {
|
||||
if n.ends_with(".md") {
|
||||
Some(n.to_owned().replace(".md",""))
|
||||
Some(n.to_owned().replace(".md", ""))
|
||||
} else {
|
||||
None
|
||||
Some(n.to_owned())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
|