aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--content/blog/miscellaneous/minecraft-server-on-nixos.md108
-rw-r--r--content/blog/miscellaneous/ods.md112
-rw-r--r--content/blog/nix/migrating-eventline.md166
-rw-r--r--search/go.mod2
4 files changed, 387 insertions, 1 deletions
diff --git a/content/blog/miscellaneous/minecraft-server-on-nixos.md b/content/blog/miscellaneous/minecraft-server-on-nixos.md
new file mode 100644
index 0000000..a2a52e9
--- /dev/null
+++ b/content/blog/miscellaneous/minecraft-server-on-nixos.md
@@ -0,0 +1,108 @@
+---
+title: Deploying a Minecraft bedrock server on NixOS
+description: How I made this work for my niece
+date: 2024-04-13
+tags:
+- Minecraft
+- nix
+---
+
+## Introduction
+
+My niece wanted to play Minecraft with me and her dad over the easter holiday. I feel that the realms official hosting are a bit expensive at 10€/month and not very flexible regarding pausing the subscription without losing your progress. We will probably stop playing when my niece has school only to pick up the game over the summer, so self hosting the game sounds a lot better.
+
+## Self hosting Minecraft bedrock
+
+### Deploying Minecraft
+
+Minecraft bedrock is really not made for things other than consoles or phones. The good thing is that some clever people made it run anyway, the bad thing is that it requires some tricks.
+
+I settled on using the [itzg/minecraft-bedrock-server](https://hub.docker.com/r/itzg/minecraft-bedrock-server) docker image with which I did not encounter any major problems. The only small issue I faced was during a Minecraft version update, for almost 48h I could not match the versions on the server, my niece's switch and my brother's PS5... but it solved itself when all devices finally agreed to be on the new release.
+
+### Resolving bedrock user names to user ids
+
+Since my niece is only eleven I wanted to lock down the server. This required finding out the Microsoft Xbox ids of each account and the main difficulty was that most guides focus on the Java version of Minecraft which relies on incompatible ids. To resolve your Xbox ids, use [this site](https://www.cxkes.me/xbox/xuid).
+
+### Making the server reachable from consoles
+
+One issue is that my niece plays on Nintendo Switch and cannot join custom servers with an IP address. I had to do some DNS shenanigans! The gist of it is that the only servers she can join are five especially "featured" servers. The console finds the IP addresses of these servers from hard coded hostnames, so by deploying my own DNS server and configuring the console to use it... I can answer my own server's IP address to one of these queries.
+
+### Minecraft on NixOS
+
+Here is the module I wrote to deploy the Minecraft container, the DNS tricks server and Borg backups:
+```nix
+{ config, pkgs, ... }:
+{
+ environment = {
+ etc = {
+ "borg-minecraft-data.key" = {
+ mode = "0400";
+ source = ./borg-data.key;
+ };
+ };
+ };
+ networking.firewall.allowedUDPPorts = [
+ 53 # DNS
+ 19132 # Minecraft
+ ];
+ services = {
+ borgbackup.jobs = let defaults = {
+ compression = "auto,zstd";
+ doInit = true;
+ encryption.mode = "none";
+ prune.keep = {
+ daily = 14;
+ weekly = 4;
+ monthly = 3;
+ };
+ startAt = "daily";
+ }; in {
+ "minecraft-data" = defaults // {
+ environment.BORG_RSH = "ssh -i /etc/borg-minecraft-data.key";
+ paths = "/srv/minecraft/worlds";
+ repo = "ssh://borg@dalinar.adyxax.org/srv/borg/minecraft-data";
+ };
+ };
+ unbound = {
+ enable = true;
+ resolveLocalQueries = false;
+ settings = {
+ server = {
+ access-control = [ "0.0.0.0/0 allow" "::/0 allow" ]; # you might now want this open for recursion for everyone
+ interface = [ "0.0.0.0" "::" ];
+ local-data = "\"mco.lbsg.net. 10800 IN A X.Y.Z.T\""; # one of the hardcoded hostnames on the console
+ local-zone = "mco.lbsg.net. static";
+ };
+ forward-zone = [
+ {
+ name = ".";
+ forward-addr = "1.1.1.1"; #cloudflare dns"; # I still want the console to be able to resolve other domains
+ }
+ ];
+ };
+ };
+ };
+ virtualisation.oci-containers.containers = {
+ minecraft = {
+ environment = {
+ ALLOW_CHEATS = "true";
+ EULA = "TRUE";
+ DIFFICULTY = "1";
+ SERVER_NAME = "My Server";
+ TZ = "Europe/Paris";
+ VERSION = "LATEST";
+ ALLOW_LIST_USERS = "adyxax:2535470760215402,pseudo2:XXXXXXX,pseudo3:YYYYYYY";
+ };
+ image = "itzg/minecraft-bedrock-server";
+ ports = ["0.0.0.0:19132:19132/udp"];
+ volumes = [ "/srv/minecraft/:/data" ];
+ };
+ };
+}
+```
+
+Note that the `X.Y.Z.T` in the configuration is the IP address from which Minecraft is reachable.
+
+## Conclusion
+
+We had quite a lot of fun with this over the holiday, and I am pleased that Minecraft is so lightweight. It should run fine on a 3$/month VPS even in the late game! If you want to host a Minecraft server I recommend giving this a try.
diff --git a/content/blog/miscellaneous/ods.md b/content/blog/miscellaneous/ods.md
new file mode 100644
index 0000000..1d2d298
--- /dev/null
+++ b/content/blog/miscellaneous/ods.md
@@ -0,0 +1,112 @@
+---
+title: A french scrabble web validator
+description: a good use for a golang static binary deployed on nixos
+date: 2024-04-03
+tags:
+- golang
+---
+
+## Introduction
+
+After seeing my parents use mobile applications full of ads just to check if a word is valid to play in the famous scrabble game (french version), I decided I could do something about it. This is a few hours project to build and deploy a small web application with just an input form and a backend that checks if words are valid or not. It is also an opportunity to look into go 1.22 stdlib routing improvements.
+
+## The project
+
+### The dictionary
+
+The "Officiel Du Scrabble" (ODS for short) is what the official dictionary for this game is called. One very sad thing is that this dictionary is not free! You cannot download it digitally, which seems crazy for a simple list of words. You might use your google-fu and maybe find it on some random GitHub account if you look for it, but I certainly did not.
+
+### The web service
+
+Here is what I have to say about this [80 lines go program](https://git.adyxax.org/adyxax/ods/tree/main.go):
+- The first lines are the necessary imports.
+- The next ones are dedicated to embedding all the files into a single binary.
+- The compilation of the HTML template follows, with the definition of a struct type necessary for its rendering.
+- Then come the two http handlers.
+- Finally the main function that defines the http routes and starts the server.
+
+While it does not feel optimal in terms of validation since I am not parsing the users' input, this input is normalized: accents and diacritics are converted to the corresponding ASCII character and spaces are trimmed at the beginning and at the end of the input. Then it is a simple matter of comparing strings while iterating over the full list of words.
+
+Building a trie would make the search a lot faster, but the simplest loop takes less than 2ms on my server and therefore is good enough for a service that will barely peak at a few requests per minutes.
+
+### Hosting
+
+I build a static binary with `CGO_ENABLED=0 go build -ldflags "-s -w -extldflags \"-static\"" .` and since there is no `/usr/local` on nixos I simply copy this static binary to `/srv/ods/ods`. The nixos way would be to write a derivation but I find it too unwieldily for such a simple use case.
+
+Here is the rest of the relevant configuration:
+
+``` nix
+{ config, lib, pkgs, ... }:
+{
+ imports = [
+ ../lib/nginx.nix
+ ];
+ services.nginx.virtualHosts = let
+ headersSecure = ''
+ # A+ on https://securityheaders.io/
+ add_header X-Frame-Options deny;
+ add_header X-XSS-Protection "1; mode=block";
+ add_header X-Content-Type-Options nosniff;
+ add_header Referrer-Policy strict-origin;
+ add_header Cache-Control no-transform;
+ add_header Content-Security-Policy "script-src 'self' 'unsafe-inline'";
+ add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
+ # 6 months HSTS pinning
+ add_header Strict-Transport-Security max-age=16000000;
+ '';
+ headersStatic = headersSecure + ''
+ add_header Cache-Control "public, max-age=31536000, immutable";
+ '';
+ in {
+ "ods.adyxax.org" = {
+ extraConfig = "error_page 404 /404.html;";
+ forceSSL = true;
+ locations = {
+ "/" = {
+ extraConfig = headersSecure;
+ proxyPass = "http://127.0.0.1:8090";
+ };
+ "/static" = {
+ extraConfig = headersStatic;
+ proxyPass = "http://127.0.0.1:8090";
+ };
+ };
+ sslCertificate = "/etc/nginx/adyxax.org.crt";
+ sslCertificateKey = "/etc/nginx/adyxax.org.key";
+ };
+ };
+ systemd.services."ods" = {
+ description = "ods.adyxax.org service";
+
+ after = [ "network-online.target" ];
+ wants = [ "network-online.target" ];
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ ExecStart = "/srv/ods/ods";
+ Type = "simple";
+ DynamicUser = "yes";
+ };
+ };
+}
+```
+
+This defines a nginx virtual host that proxifies requests to our service, along with a systemd unit that will ensure our service is running.
+
+### DNS
+
+My DNS records are set via OpenTofu (terraform) and look like:
+
+``` hcl
+resource "cloudflare_record" "ods-cname-adyxax-org" {
+ zone_id = lookup(data.cloudflare_zones.adyxax-org.zones[0], "id")
+ name = "ods"
+ value = "myth.adyxax.org"
+ type = "CNAME"
+ proxied = false
+}
+```
+
+## Conclusion
+
+This was a fun little project, it is live at https://ods.adyxax.org/. Go really is a good choice for such self contained little web services.
diff --git a/content/blog/nix/migrating-eventline.md b/content/blog/nix/migrating-eventline.md
new file mode 100644
index 0000000..0162bd7
--- /dev/null
+++ b/content/blog/nix/migrating-eventline.md
@@ -0,0 +1,166 @@
+---
+title: Migrating eventline to nixos
+description: How I migrated my eventline installation to nixos
+date: 2024-03-22
+tags:
+- eventline
+- nix
+---
+
+## Introduction
+
+I am migrating several services from a FreeBSD server to a nixos server. Here is how I performed the operation for [eventline](https://www.exograd.com/products/eventline/).
+
+## Eventline on nixos
+
+Eventline is not packaged on nixos, so that might be a good project to try and tackle in the near future. In the meantime I used the container image.
+
+Here is the module I wrote to deploy an eventline container, configure postgresql and borg backups:
+```nix
+{ config, lib, pkgs, ... }:
+{
+ imports = [
+ ../../lib/postgresql.nix
+ ];
+ environment.etc = {
+ "borg-eventline-db.key" = {
+ mode = "0400";
+ source = ./borg-db.key;
+ };
+ "eventline.yaml" = {
+ mode = "0400";
+ source = ./eventline.yaml;
+ uid = 1000;
+ };
+ "eventline-entrypoint" = {
+ mode = "0500";
+ source = ./eventline-entrypoint;
+ uid = 1000;
+ };
+ };
+ services = {
+ borgbackup.jobs = let defaults = {
+ compression = "auto,zstd";
+ doInit = true;
+ encryption.mode = "none";
+ prune.keep = {
+ daily = 14;
+ weekly = 4;
+ monthly = 3;
+ };
+ startAt = "daily";
+ }; in {
+ "eventline-db" = defaults // {
+ environment.BORG_RSH = "ssh -i /etc/borg-eventline-db.key";
+ paths = "/tmp/eventline.sql";
+ postHook = "rm -f /tmp/eventline.sql";
+ preHook = ''rm -f /tmp/eventline.sql; /run/current-system/sw/bin/pg_dump -h localhost -U eventline -d eventline > /tmp/eventline.sql'';
+ repo = "ssh://borg@gcp.adyxax.org/srv/borg/eventline-db";
+ };
+ };
+ nginx.virtualHosts = let
+ headersSecure = ''
+ # A+ on https://securityheaders.io/
+ add_header X-Frame-Options deny;
+ add_header X-XSS-Protection "1; mode=block";
+ add_header X-Content-Type-Options nosniff;
+ add_header Referrer-Policy strict-origin;
+ add_header Cache-Control no-transform;
+ add_header Content-Security-Policy "script-src 'self'";
+ add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
+ # 6 months HSTS pinning
+ add_header Strict-Transport-Security max-age=16000000;
+ '';
+ headersStatic = headersSecure + ''
+ add_header Cache-Control "public, max-age=31536000, immutable";
+ '';
+ in {
+ "eventline.adyxax.org" = {
+ forceSSL = true;
+ locations = {
+ "/" = {
+ extraConfig = headersSecure;
+ proxyPass = "http://127.0.0.1:8087";
+ };
+ };
+ sslCertificate = "/etc/nginx/adyxax.org.crt";
+ sslCertificateKey = "/etc/nginx/adyxax.org.key";
+ };
+ "eventline-api.adyxax.org" = {
+ locations = {
+ "/" = {
+ extraConfig = headersSecure;
+ proxyPass = "http://127.0.0.1:8085";
+ };
+ };
+ onlySSL = true;
+ sslCertificate = "/etc/nginx/adyxax.org.crt";
+ sslCertificateKey = "/etc/nginx/adyxax.org.key";
+ };
+ };
+ postgresql = {
+ ensureDatabases = ["eventline"];
+ ensureUsers = [{
+ name = "eventline";
+ ensureDBOwnership = true;
+ }];
+ };
+ };
+ virtualisation.oci-containers.containers = {
+ eventline = {
+ image = "exograd/eventline:1.1.0";
+ ports = [
+ "127.0.0.1:8085:8085" # api
+ "127.0.0.1:8087:8087" # web
+ ];
+ user = "root:root";
+ volumes = [
+ "/etc/eventline.yaml:/etc/eventline/eventline.yaml:ro"
+ "/etc/eventline-entrypoint:/usr/bin/entrypoint:ro"
+ ];
+ };
+ };
+}
+```
+
+## Dependencies
+
+The dependencies are mostly the same as in [my article about vaultwarden migration]({{< ref "migrating-vaultwarden.md" >}}#dependencies). One key difference is that there are two nginx virtual hosts and a bunch of files I need for eventline.
+
+## Migration process
+
+The first step is obviously to deploy this new configuration to the server, then I need to login and manually restore the backups.
+```sh
+make run host=dalinar.adyxax.org
+```
+
+The container will be failing because no password is set on the database user yet, so I stop it:
+```sh
+systemctl stop podman-eventline
+```
+
+There is only one backup job for eventline and it holds a dump of the database:
+```sh
+export BORG_RSH="ssh -i /etc/borg-eventline-db.key"
+borg list ssh://borg@gcp.adyxax.org/srv/borg/eventline-db
+borg extract ssh://borg@gcp.adyxax.org/srv/borg/eventline-db::dalinar-eventline-db-2023-11-20T00:00:01
+psql -h localhost -U postgres -d eventline
+```
+
+Restoring the data itself is done with the psql shell:
+```sql
+ALTER USER eventline WITH PASSWORD 'XXXXXX';
+\i tmp/eventline.sql
+```
+
+Afterwards I clean up the database dump and restart eventline:
+```sh
+rm -rf tmp/
+systemctl start podman-eventline
+```
+
+To wrap this up I migrate the DNS records to the new host, update my monitoring system and clean up the jail on the FreeBSD server.
+
+## Conclusion
+
+I did all this in november, I still have quite the backlog of articles to write about nix!
diff --git a/search/go.mod b/search/go.mod
index e241362..1828e5a 100644
--- a/search/go.mod
+++ b/search/go.mod
@@ -1,6 +1,6 @@
module git.adyxax.org/adyxax/www/search
-go 1.22.1
+go 1.22.2
require github.com/stretchr/testify v1.9.0