aboutsummaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
Diffstat (limited to 'content')
-rw-r--r--content/blog/miscellaneous/ods.md112
1 files changed, 112 insertions, 0 deletions
diff --git a/content/blog/miscellaneous/ods.md b/content/blog/miscellaneous/ods.md
new file mode 100644
index 0000000..e88b6c4
--- /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 dictionnary
+
+The "Officiel Du Scrabble" (ODS for short) is what the official dictionnary for this game is called. One very sad thing is that this dictionnary 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 trimed 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 unwieldly 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 virtualhost that proxifies requests to our service, along with a systemd unit that will ensute 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.