← Writing

Project journal

Building my personal Swiss Army Knife website

It's been a while I had the idea of building a website to centralize services that I generally go online to find. You know things like password generation, time converter, rolling dice, getting your IP, DNS lookup, and more.

Having this Swiss Army Knife website would likely save me some time, but also it would be an opportunity for me to practice my skills. And who knows, maybe I could monetize it a little as well.

To decide how I would implement it, I made a rough list of all the things I'd like to see on it.

  • Password generation.
  • Date, time, and timestamp conversion.
  • Rolling dice, d2 to d100.
  • Straight file sharing of misc free resources like pdf, or others.
  • Getting IP.
  • DNS resolution.
  • Mirroring broken websites (like cssmatic.com).
  • Maybe some math stuff like number pi decimals, random number generators, or prime numbers.
  • And some more.

I considered for a moment using things like surge.sh, platform.sh, or maybe glitch.com. They are all cool, but not flexible enough. Like for getting IP, I would not reinvent the wheel and just use https://github.com/mpolden/echoip, and also for serving files I would need a simple web server. So nginx it is!

Behind nginx, I can plug any system I want and in a pretty flexible way.

Aside nginx, I also chose to:

  • use go for the backend API,
  • and use dart for the frontend.

I didn't want to do JavaScript nor TypeScript. But try something else. And I've always liked Bob Nystrom one of the developers of Dart. Also, Dart is mature and popular nowadays thanks to flutter, so it's a good moment.

My long term goals and ambitions are: - Open source all the code - Infrastructure as code - CI/CD - Neat swagger specifications for all services, hence nice documentation - Monitoring - Scalability - Good sleep - Some monetization

So yeah, something solid that can run by itself and let me sleep nicely!

Also, I guess I'll try to do a logo for the website myself at some point and document that process. To show you that sometimes as an engineer you don't need help from a designer.

First milestone: v0.0.1

Here is my first milestone. Which is a very early version of the architecture and the rolling dice service. The easiest to start with ;P

I would like to present you the v0.0.1 of my Swiss Army Knife services website!

Logo

I decided to call it utile.space.

Utile because it is a synonym of useful and comes from French! And .space because it sounded adequate.

Also, I made the source code freely available: * https://github.com/SonnyAD/utile-front The front-end made with bare Dart * https://github.com/SonnyAD/utile-nginx The NGINX configuration * https://github.com/SonnyAD/utile-api The Golang API backend serving the website, but also aimed to be useable programmatically by anybody

As I introduced above, I am going to share with you my development journal.

Note that there is already too much to talk about in this version. So I am going to split my journey into many articles.

Start easy with NGINX

I started the journey by getting a new NGINX server conf for my new domain utile.space. And setup SSL with certbot like described in my article.

Read on sonny.alvesdi.as ↗

NGINX
server {

   server_name utile.space;

   location /ip/ {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:8080/; 
   }

    location /pdf/ {
        alias /usr/share/nginx/html/pdf/;
        autoindex on;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/utile.space/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/utile.space/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

You can notice two extras things.

Sharing PDF files

I have lots of free and useful PDF resources that I wanted to reshare. So I declared a location to allow me to share them. Note that to make those files visible publicly, I used the instructions: autoindex on.

See the results at https://utile.space/pdf/

Get your IP service

I already had my tool to get IP on ip.alvesdi.as from the following article.

Read on sonny.alvesdi.as ↗

So I added a reverse proxy to reuse it. I got some issues because I was missing a trailing slash on the reverse proxy and location, I believe. Something I should investigate later to properly understand the problem.

See the results at: https://ip.alvesdi.as

Then to continue easy, the API

I had a sample go project running on Docker in my personal library. So I started from that template.

Docker

Here was the Dockerfile:

DOCKERFILE
FROM golang:latest AS build
WORKDIR /go/src

COPY server.go .
RUN CGO_ENABLED=0 go build server.go

FROM scratch AS runtime
COPY --from=build /go/src/server /
EXPOSE 3000/tcp
CMD ["/server"]

I use the latest golang image to compile server.go into a binary. Then I copy that binary into a new image based on scratch. Scratch, which is an empty image basically.

You may have noticed I prepend CGO_ENABLED=0 in front of go build. This is to make sure the binary would be compatible with the scratch image. Otherwise, it would not.

Go code

I started with something easy. I started with the rolling dice feature. My goal was to have an endpoint that could allow me to roll a die of any faces between 2 and 100.

Please find the code below.

GOLANG
package main

import (
  "fmt"
  "net/http"
  "math/rand"
  "strconv"
  "regexp"
)

func home(w http.ResponseWriter, r *http.Request) {

  // evaluate only if i in [2-100]
  re := regexp.MustCompile("/d(100|1[0-9]|[2-9][0-9]?)")
  match := re.FindStringSubmatch(r.URL.Path)

  if match == nil {
    http.Error(w, "Bad request", http.StatusBadRequest)
    return
  }

  dice, err := strconv.Atoi(match[1])

  if err != nil {
    http.Error(w, "Unknown error", http.http.StatusInternalError)
    return
  }

  fmt.Fprintf(w, strconv.Itoa(rand.Intn(dice)+1))
}

func main() {
    http.HandleFunc("/", home)
    http.ListenAndServe(":3000", nil)
}

I went with one endpoint only and would parse the location using a regular expression. For those allergic to regular expression, sorry, that's kind of my kink ^^

Anyway, let me explain it. "/d(100|1[0-9]|[2-9][0-9]?)" means first I am expecting to receive a request that will start with /d. Then in parentheses, I specified 3 different cases, consider the | as a simple "or". Then the request is either /d100, between /d10 to /d19, or finally between /d2 to /d9, and because of the ?, /d20 to /d99. I end up covering any case between /d2 to /d100.

With the regex, I capture the number n of the faces. And I use it with the function rand.Intn to generate a pseudo-random number. I also add 1 to it to make sure the result is between 1 and n.

Plug it with NGINX

With that, I ran a docker container on my server and plugged it into a new location in my NGINX server conf.

NGINX
   location /api/ {
        proxy_pass         http://127.0.0.1:3000/; 
   }

Conclusion

As I said, I am doing this as a hobby, so it has to give me quick results and not take me too much time to develop. So I started easy ;)

In the next articles, I will describe the following of my journey. I will discuss the beginning of my experience with Dart and the first problems I had on the front-end. I will also go through the deployment scripts I wrote to have a rough automated process to publish my work. Lastly, I will explain how I made the logo, I am not a designer, and I think I came with something not bad. And I want to show you it's not that complicated to do.


Photo by Patrick on Unsplash

GOOD THINGS START WITH A CONVERSATION.

Something on your mind?

LinkedIn
sonny@the-internet: ~

A small window into a curious mind.

Type help to look around. No serious hacking required.

↑ ↓ command historytry a little curiosity