Base API: Making the self hosted version
Tags: crystallang, mintlang, baseapi, sideprojects
Published: 2019-10-22 06:43:26

In my [previous post](https://dev.to/gdotdesign/base-api-self-hosted-ggn) I announced that Base API can now be self-hosted. 

In this post I will show how I managed to compile the same application that is running as a SaaS software to be able to run as a single binary (with 90% of the same functionality).

The goal was to have one code-base for both versions, to keep them in sync feature wise and to make maintaining easier.

## Stack

Currently Base API is running in two places:

* On Heroku - the backend application is compiled on deploy and the binary is running in a dyno. The only thing needed there is the [Crystal Buildpack](https://github.com/crystal-lang/heroku-buildpack-crystal)
* On Netlify - the frontend of the application is built on Netlify and served as a Progressive Web App

## Self-Hosted vs. SaaS

The SaaS version has some features that are not needed in the self-hosted version:

* **authentication and user management** - since it's a service people are able to sign up, login, etc... 
* **limits on services** - rate-limiting and email sending limits
* **error reporting** - runtime errors are reported to [Sentry](https://sentry.io)

Also the Self-Hosted version contains it's own features:

* **status of the server** - a dashboard that lists information about the server
* **user and password authentication for the interface** 

## Crystal 

When I started to research I had some options to handle the two different versions:

* **use an environment variable at runtime** - this is easiest version to go with, the downside is that all of the application is compiled into the binary and by using the right environment variable the users could host their SaaS version which is not desired.

* [**use a compile time flag**](https://crystal-lang.org/reference/syntax_and_semantics/compile_time_flags.html) - this is the more complicated version, it has the upside of the binary only containing the code for the self-hosted version, but it's harder to test and compile.

I went with the **compile time flag** which can be used in [macros](https://crystal-lang.org/reference/syntax_and_semantics/macros.html) like this:

```crystal
{% if flag?(:standalone) %}
  # This will be in self-hosted version
{% else %}
  # This will be in the SaaS version
{% end %}
```

So in every case there is something present in one version and not the other version a macro like that needed to be added.

At the end I just need to compile with the flag: 

```
crystal build src/base.cr -D standalone
```

and Crystal would do it's magic.

## Mint

Since Mint does not have a macro system, the only way to go is with [environment variables](https://guide.mint-lang.com/reference/environment-variables).

To check which version we are in I created a computed property on the main store:

```mint
store Application {
  get isStandalone : Bool {
    !String.isEmpty(@STANDALONE) 
  }

  ...
}
```

And everywhere I needed to check I just used that property for deciding what to do, for example on one of the routes:

```mint
/download {
  if (Application.isStandalone) {
    Window.navigate("/")
  } else {
    sequence {
      Application.initialize()
      Application.setPage(Page::Download)
    }
  }
}
```

You can provide the environment variables using a `.env` file when building:

```
mint build -e .env.standalone
```

## Putting them together

Crystal has a nice shard for exactly this purpose: [https://github.com/schovi/baked_file_system](https://github.com/schovi/baked_file_system) it allows you to "bake" files into the compiled binary.

The frontend after compiling, its baked into the final binary and served through a custom HTTP handler.

The **static binary** is compiled in a Docker container on Alpine linux.

## Licensing

I needed to figure out how to limit people for using the software without permission, and for this I used [Public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography).

There is a key pair - the public key is built into the binary and the private key is supplied to the server. 

Every 10 minutes or so the self-hosted version checks if it can keep running or not (stops if any of the steps fail):
* it sends a request to the server with a unique id and a license key (which represents the user)
* the server then checks if the license key is valid or not
* if it's not valid then sends an error
* if it's valid then sings the payload with its private key and sends the payload back with along the signature
* the self-hosted server checks the signatures validity

This way I can check the validity of the license and if it's invalid the self-hosted server will just stop, also it is very hard to circumvent since it needs an active internet connection and the requests are random every time.

## UX

So now I had the binary but it was not easy to use so I added a CLI to do migrations and start the server. The configuration can be read from a YAML file or from environment variables. Also logging needed to be unified and configurable.

--------------

It took around two days to do the modifications, if you have any questions let me know!