Writing a Dispatch API Backend Server in Go


I first encountered the programming language Go back in 2015 while making a Github issue for Rclone. Since then I’ve used tools like Fathom, gowitness, Restic and Wpress-Extractor all written in Go. Learning Go has been on my todo list for a while now. During Christmas break 🎄- the span of 2 weeks – I’ve finally dove deep into Go and managed to cobble together my first Go application, a dispatch API server. If you want the short version scroll down and watch the video otherwise proceed onward. 💥

I learn best while working on real projects.

This forces me to read tutorials for just the parts I don’t understand. Warning, I’m still very much a Go newbie ⚠. The final code is a hackenstein created by piecing together a bunch of other people’s example code referenced at the bottom of the this post. I don’t really know what I’m doing but surprisingly the final result actually works! 😁

Building a dispatch API for my WordPress management toolkit.

The goal of making a dispatch API server was to solve two problems with CaptainCore, my WordPress management toolkit.

  1. Remove phpseclib implementation. My management GUI sends commands to my management CLI via a phpseclib SSH connection. That SSH connection is slow, overkill and totally unnecessary.
  2. Support multiple actions. My management GUI only works if you click things one at a time. Doing a bunch of actions at once would have random results due to multiple SSH tunnels being created and no error handling or tracking requests.

Putting a dispatch API server between my management GUI and CLI significantly increases performance and properly manages requests.

Why write this in Go?

It’s true I could have written this API using PHP or any other web framework/language with Nginx or Apache. Go is compiled language. A compiled application is very efficient and fully self-contained. No need for a web server as it’s all included. I’ve been very impressed how efficient a Go app like Fathom is. My self-hosted Fathom instance is collecting over 4 million pageviews of stats per month on a cheap $5/month Digital Ocean VPS. I figured Go could easily handle a simple dispatch API. Go applications can be compiled to run on Windows, Mac and Linux. The idea is write the code once and run it wherever you like.

There’s a Go package for that.

Go comes with a bunch of packages you can include from their standard library. You can also include any other custom package simply by reference it by it’s Github URL. For this application I’ll highlight the following packages.

  • Autocert – Let’s Encrypt package (part of the standard library)
  • x509 – Parses SSL certs keys and certificates (part of the standard library)
  • Cobra – CLI package
  • Gorm – ORM library package
  • Mux – HTTP Router package
  • Handlers – HTTP handlers package

Storing basic configurations in a JSON file named config.json

Below is a sample file. It’s a fairly simple setup that defines a token, host, port and SSL mode whether development or production. I could easily add more configurations as needed later.

{
    "token":"random_token_key_please_change_me",
    "host":"localhost",
    "port":"1234",
    "ssl_mode":"development"
}

These configurations are loaded into a variable named config accessible throughout my Go application.

var config = LoadConfiguration("config.json")

type Config struct {
	Token   string `json:"token"`
	Host    string `json:"host"`
	Port    string `json:"port"`
	SSLMode string `json:"ssl_mode"`
}

func LoadConfiguration(file string) Config {
	var config Config
	configFile, err := os.Open(file)
	defer configFile.Close()
	if err != nil {
		fmt.Println(err.Error())
	}
	jsonParser := json.NewDecoder(configFile)
	jsonParser.Decode(&config)
	return config
}

Starting the web server from the command line

Cobra is a fantastic package for handling CLI commands and arguments. This is a very simply app so it really only has one command server however that can easily be expanded for additional features.

This entire CLI is created with very little code.

func serverCmd() *cobra.Command {
	return &cobra.Command{
		Use: "server",
		RunE: func(cmd *cobra.Command, args []string) error {

			// Handle Subsequent requests
			handleRequests()

			return nil
		},
	}
}

cmd := &cobra.Command{
    Use:     "captaincore-dispatch",
    Short:   "CaptainCore Dispatch Server 💻",
    Version: "0.1",
}

cmd.AddCommand(serverCmd())

if err := cmd.Execute(); err != nil {
    //fmt.Println(err)
    os.Exit(0)
}

Getting HTTPS working in development and production

This actually took the most amount of time to figure out. There are lots of tutorials out there which focus on parts of HTTPS however some are outdated and I couldn’t find a single explanation of how to pull this off in Go. After a lot of trial and error I was able to get HTTPS working both in development and production.

For development I followed the general idea Delicious Brains lays out which is to generate a Certificate Authority (CA), which is installed locally, then generate self-signed certificate based on the CA. Since the CA is trusted locally then all self signed certificates created based on that CA will automatically be trusted by your computer. This is all doable in Go following some fancy code generateCertificateAuthority() and generateCert().

func generateCertificateAuthority() {
	ca := &x509.Certificate{
		SerialNumber: big.NewInt(1653),
		Subject: pkix.Name{
			Organization:  []string{"CaptainCore"},
			Country:       []string{"USA"},
			Province:      []string{"PA"},
			Locality:      []string{"Lancaster"},
			StreetAddress: []string{"342 N Queen St"},
			PostalCode:    []string{"17603"},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		IsCA:                  true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
	}

	priv, _ := rsa.GenerateKey(rand.Reader, 2048)
	pub := &priv.PublicKey
	caB, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
	if err != nil {
		log.Println("create ca failed", err)
		return
	}

	// Public key
	certOut, err := os.Create("certs/ca.crt")
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: caB})
	certOut.Close()
	log.Print("written certs/cat.crt\n")

	// Private key
	keyOut, err := os.OpenFile("certs/ca.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
	keyOut.Close()
	log.Print("written certs/ca.key\n")
}

func generateCert() {

	// Load CA
	catls, err := tls.LoadX509KeyPair("certs/ca.crt", "certs/ca.key")
	if err != nil {
		panic(err)
	}
	ca, err := x509.ParseCertificate(catls.Certificate[0])
	if err != nil {
		panic(err)
	}

	// Prepare certificate
	cert := &x509.Certificate{
		SerialNumber: big.NewInt(1658),
		Subject: pkix.Name{
			Organization:  []string{"CaptainCore"},
			Country:       []string{"USA"},
			Province:      []string{"PA"},
			Locality:      []string{"Lancaster"},
			StreetAddress: []string{"342 N Queen St"},
			PostalCode:    []string{"17603"},
			CommonName:    "CaptainCore",
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		SubjectKeyId:          []byte{1, 2, 3, 4, 6},
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageDigitalSignature,
		BasicConstraintsValid: true,
		DNSNames:              []string{"localhost"},
	}

	priv, _ := rsa.GenerateKey(rand.Reader, 2048)
	pub := &priv.PublicKey

	// Sign the certificate
	certB, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, catls.PrivateKey)

	// Public key
	certOut, err := os.Create("certs/cert.pem")
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certB})
	certOut.Close()
	log.Print("written certs/cert.pem\n")

	// Private key
	keyOut, err := os.OpenFile("certs/key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
	keyOut.Close()
	log.Print("written certs/key.pem\n")
}

In production mode I generate a free Let’s Encrypt SSL. Without getting too in-depth with code, I’ll simply reference this blog post on the topic.

Let’s Encrypt requires that the initial DNS verification be made to an HTTP web server on port 80. Luckily in Go it’s quite easy to spin up multiple web servers. To accommodate Let’s Encrypt I spin up my dispatch API on HTTPS port 443 and the certification verification files on HTTP port 80. That means the only thing viewable via HTTP are the Let’s Encrypt verifications files.

When launching multiple web servers in Go the first one needs to be started in the background so that it doesn’t block spinning up the second one. This one had initially had me stumped however it’s actually quite easy to do. In Go wrap the first server startup in a go func() { }() which will run that code concurrently with the second.

// Launch HTTP server
go func() {

    fmt.Println("Starting server http://localhost")

    err := httpSrv.ListenAndServe()
    if err != nil {
        log.Fatalf("httpSrv.ListenAndServe() failed with %s", err)
    }

}()

// Launch HTTPS server
fmt.Println("Starting server https://" + config.Host + ":" + config.Port)
log.Fatal(httpsSrv.ListenAndServeTLS("", ""))

Storing data into database with GORM.

Go is all about data structures. You define custom data structures with the struct type. I use the following Go struct to track incoming API requests. By default Go stores everything in memory which means the data won’t persist if the server is ever restarted. That’s not helpful for a real world application so let’s put this data in a database.

type Task struct {
	gorm.Model
	Command  string
	Status   string
	Response string
}

Gorm makes it super easy to work with databases. The gorm.Model field shown above will magically transform the Task struct into a tasks table. Gorm handles all of the SQL for generating the tables and fields.

Want to add a field to the database? Simply add the field to the Go struct and restart the application. Gorm will see the new fields and automatically configure the database. I’m using Sqlite for simplicity however Gorm supports many types of databases. If you want to have your mind blown then watch this video and this video on Gorm.

var db *gorm.DB
var err error

func initialMigration() {

	// Migrate the schema
	db.AutoMigrate(&Task{})

}

db, err = gorm.Open("sqlite3", "sql.db")
if err != nil {
	panic("failed to connect database")
}
defer db.Close()

initialMigration()

Testing and developing API endpoints with Insomnia.

There are 2 endpoints for creating new tasks /run and /tasks. The run endpoint immediately will run the command and send back the response whereas the tasks endpoint will run the command in the background. For developing endpoints I recommend using Insomnia. Here is what a basic request/response looks like with Insomnia.

On the server this adds it as a new task and starts actually running the command. Once completed it updates the task status and response accordingly as shown here.

Crude authentication with a header token

One thing I haven’t spend much time on is authentication. I’ve implemented a crude authentication by requiring a token in each request header. This approach is not scalable and generally not recommended however for my basic application, where I’m the only user of the API, I feel it’s fine.

In code all of my custom routes are wrapped in a checkSecurity function which simply checks for a valid header as defined in the config.json file.

func checkSecurity(next httpHandlerFunc) httpHandlerFunc {
	return func(res http.ResponseWriter, req *http.Request) {
		header := req.Header.Get("token")
		if header != config.Token {
			res.WriteHeader(http.StatusUnauthorized)
			res.Write([]byte("401 - Unauthorized"))
			return
		}
		next(res, req)
	}
}

router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/task/{id}", checkSecurity(viewTask)).Methods("GET")
router.HandleFunc("/task/{id}", checkSecurity(updateTask)).Methods("PUT")
router.HandleFunc("/task/{id}", checkSecurity(deleteTask)).Methods("DELETE")
router.HandleFunc("/tasks", checkSecurity(newTask)).Methods("POST")
router.HandleFunc("/tasks", checkSecurity(allTasks)).Methods("GET")
router.HandleFunc("/tasks/{page}", checkSecurity(allTasks)).Methods("GET")
router.HandleFunc("/run", checkSecurity(newRun)).Methods("POST")

Making requests from WordPress with wp_remote_post

Within WordPress a request would like the following with CAPTAINCORE_CLI_TOKEN and CAPTAINCORE_CLI_ADDRESS being defined by the wp-config.php file.

$data = array( 
    'headers' => array(
        'Content-Type' => 'application/json; charset=utf-8', 
        'token'        => CAPTAINCORE_CLI_TOKEN 
    ), 
    'body' => json_encode( array(
        "command" => $command 
    )), 
    'method'      => 'POST', 
    'data_format' => 'body' 
);

if ( $run_in_background ) {

    // Add command to dispatch server
    $response = wp_remote_post( CAPTAINCORE_CLI_ADDRESS . "/tasks", $data );
    $response = json_decode( $response["body"] );
    
    // Response with task id
    if ( $response && $response->task_id ) { 
        echo $response->task_id; 
    }


    wp_die(); // this is required to terminate immediately and return a proper response
}

// Add command to dispatch server
$response = wp_remote_post( CAPTAINCORE_CLI_ADDRESS . "/run", $data );
$response = json_decode( $response["body"] );

Running commands with os/exec package

Go can run a command line utility using the os/exec package. That said it’s very particular and sorta difficult to use. You can’t just say run a command like this.

captaincore update <site-name> --exclude-plugins=woocommerce

You instead need to break down this command into an array separating the various arguments. That would look something like this.

exec.Command("captaincore", "update", "<site-name>", "--exclude-plugins=woocommerce")

In order to break apart the command into something Go can understand I decided to use a regular expression to parse the command. That dynamically builds an array which Go can understand and execute.

func runCommand(cmd string, t Task) string {

	// See https://regexr.com/4154h for custom regex to parse commands
	// Inspired by https://gist.github.com/danesparza/a651ac923d6313b9d1b7563c9245743b
	pattern := `(--[^\s]+="[^"]+")|"([^"]+)"|'([^']+)'|([^\s]+)`
	parts := regexp.MustCompile(pattern).FindAllString(cmd, -1)

	//	The first part is the command, the rest are the args:
	head := parts[0]
	arguments := parts[1:len(parts)]

	//	Format the command
	command := exec.Command(head, arguments...)

	//	Sanity check -- capture stdout and stderr:
	var out bytes.Buffer
	var stderr bytes.Buffer
	command.Stdout = &out    // Standard out: out.String()
	command.Stderr = &stderr // Standard errors: stderr.String()

	//	Run the command
	command.Run()

	t.Status = "Completed"

	// Add results to db if in JSON format
	if isJSON(out.String()) {
		t.Response = out.String()
	}

	db.Save(&t)

	return out.String()

}

// Starts running CaptainCore command
response := runCommand("captaincore "+task.Command, task)

Video walkthrough showing the dispatch API in action.

Video Thumbnail
CaptainCore Dispatch Overview

And finally, the whole code. 🚀

You can play around with this code on your own by copying the following into a file named captaincore-dispatch.go and then run go run captaincore-dispatch.go. You will need to first need to install Go and fetch the additional libraries with go get. Enjoy!

package main

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"log"
	"math/big"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"time"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	"github.com/spf13/cobra"
	"golang.org/x/crypto/acme/autocert"
)

var db *gorm.DB
var err error
var config = LoadConfiguration("config.json")

type httpHandlerFunc func(http.ResponseWriter, *http.Request)

const (
	htmlIndex = `<html><body>Welcome!</body></html>`
)

type Config struct {
	Token   string `json:"token"`
	Host    string `json:"host"`
	Port    string `json:"port"`
	SSLMode string `json:"ssl_mode"`
}

type Task struct {
	gorm.Model
	Command  string
	Status   string
	Response string
}

func LoadConfiguration(file string) Config {
	var config Config
	configFile, err := os.Open(file)
	defer configFile.Close()
	if err != nil {
		fmt.Println(err.Error())
	}
	jsonParser := json.NewDecoder(configFile)
	jsonParser.Decode(&config)
	return config
}

func generateCertificateAuthority() {
	ca := &x509.Certificate{
		SerialNumber: big.NewInt(1653),
		Subject: pkix.Name{
			Organization:  []string{"CaptainCore"},
			Country:       []string{"USA"},
			Province:      []string{"PA"},
			Locality:      []string{"Lancaster"},
			StreetAddress: []string{"342 N Queen St"},
			PostalCode:    []string{"17603"},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		IsCA:                  true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
	}

	priv, _ := rsa.GenerateKey(rand.Reader, 2048)
	pub := &priv.PublicKey
	caB, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
	if err != nil {
		log.Println("create ca failed", err)
		return
	}

	// Public key
	certOut, err := os.Create("certs/ca.crt")
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: caB})
	certOut.Close()
	log.Print("written certs/cat.crt\n")

	// Private key
	keyOut, err := os.OpenFile("certs/ca.key", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
	keyOut.Close()
	log.Print("written certs/ca.key\n")
}

func generateCert() {

	// Load CA
	catls, err := tls.LoadX509KeyPair("certs/ca.crt", "certs/ca.key")
	if err != nil {
		panic(err)
	}
	ca, err := x509.ParseCertificate(catls.Certificate[0])
	if err != nil {
		panic(err)
	}

	// Prepare certificate
	cert := &x509.Certificate{
		SerialNumber: big.NewInt(1658),
		Subject: pkix.Name{
			Organization:  []string{"CaptainCore"},
			Country:       []string{"USA"},
			Province:      []string{"PA"},
			Locality:      []string{"Lancaster"},
			StreetAddress: []string{"342 N Queen St"},
			PostalCode:    []string{"17603"},
			CommonName:    "CaptainCore",
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(10, 0, 0),
		SubjectKeyId:          []byte{1, 2, 3, 4, 6},
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageDigitalSignature,
		BasicConstraintsValid: true,
		DNSNames:              []string{"localhost"},
	}

	priv, _ := rsa.GenerateKey(rand.Reader, 2048)
	pub := &priv.PublicKey

	// Sign the certificate
	certB, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, catls.PrivateKey)

	// Public key
	certOut, err := os.Create("certs/cert.pem")
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certB})
	certOut.Close()
	log.Print("written certs/cert.pem\n")

	// Private key
	keyOut, err := os.OpenFile("certs/key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
	keyOut.Close()
	log.Print("written certs/key.pem\n")
}

func allTasks(w http.ResponseWriter, r *http.Request) {
	var tasks []Task
	vars := mux.Vars(r)
	page, _ := strconv.Atoi(vars["page"])
	if page > 0 {
		offset := page * 10
		db.Offset(offset).Limit(10).Order("created_at desc").Find(&tasks)
	} else {
		db.Limit(10).Order("created_at desc").Find(&tasks)
	}

	json.NewEncoder(w).Encode(tasks)
}

func newRun(w http.ResponseWriter, r *http.Request) {
	var task Task
	json.NewDecoder(r.Body).Decode(&task)

	task.Status = "Started"

	db.Create(&task)

	// Starts running CaptainCore command
	response := runCommand("captaincore "+task.Command, task)
	fmt.Fprintf(w, response)

}

func newTask(w http.ResponseWriter, r *http.Request) {
	var task Task
	json.NewDecoder(r.Body).Decode(&task)

	task.Status = "Started"

	db.Create(&task)
	taskID := strconv.FormatUint(uint64(task.ID), 10)
	response := "{ \"task_id\" : " + taskID + "}"
	fmt.Fprintf(w, response)

	// Starts running CaptainCore command
	go runCommand("captaincore "+task.Command, task)

}

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

	vars := mux.Vars(r)
	command := vars["command"]

	var tasks Task
	db.Where("command = ?", command).Find(&tasks)
	db.Delete(&tasks)

	fmt.Fprintf(w, "Successfully Deleted Task")
}

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

	vars := mux.Vars(r)
	id := vars["id"]

	var tasks Task
	db.Where("id = ?", id).Find(&tasks)
	fmt.Println("{}", tasks)
	json.NewEncoder(w).Encode(tasks)
}

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

	vars := mux.Vars(r)
	command := vars["command"]

	var tasks Task
	db.Where("command = ?", command).Find(&tasks)

	tasks.Command = command

	db.Save(&tasks)
	fmt.Fprintf(w, "Successfully Updated Task")
}

func handleRequests() {

	var httpsSrv *http.Server
	var httpSrv *http.Server
	var m *autocert.Manager

	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/task/{id}", checkSecurity(viewTask)).Methods("GET")
	router.HandleFunc("/task/{id}", checkSecurity(updateTask)).Methods("PUT")
	router.HandleFunc("/task/{id}", checkSecurity(deleteTask)).Methods("DELETE")
	router.HandleFunc("/tasks", checkSecurity(newTask)).Methods("POST")
	router.HandleFunc("/tasks", checkSecurity(allTasks)).Methods("GET")
	router.HandleFunc("/tasks/{page}", checkSecurity(allTasks)).Methods("GET")
	router.HandleFunc("/run", checkSecurity(newRun)).Methods("POST")

	if config.SSLMode == "development" {

		// Generate ca.crt and ca.key if not found
		caFile, err := os.Open("certs/ca.crt")
		if err != nil {
			generateCertificateAuthority()
		}
		defer caFile.Close()

		// Generate cert.pem and key.pem for https://localhost
		generateCert()

		// Launch HTTPS server
		fmt.Println("Starting server https://" + config.Host + ":" + config.Port)
		log.Fatal(http.ListenAndServeTLS(":"+config.Port, "certs/cert.pem", "certs/key.pem", handlers.LoggingHandler(os.Stdout, router)))

	}
	if config.SSLMode == "production" {

		// Manage Let's Encrypt SSL

		// Note: use a sensible value for data directory
		// this is where cached certificates are stored

		httpsSrv = &http.Server{
			ReadTimeout:  5 * time.Second,
			WriteTimeout: 5 * time.Second,
			IdleTimeout:  120 * time.Second,
			Handler:      router,
		}

		//  handlers.LoggingHandler(os.Stdout, router

		dataDir := "certs/"
		hostPolicy := func(ctx context.Context, host string) error {
			// Note: change to your real domain
			allowedHost := config.Host
			if host == allowedHost {
				return nil
			}
			return fmt.Errorf("acme/autocert: only %s host is allowed", allowedHost)
		}

		m = &autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			HostPolicy: hostPolicy,
			Cache:      autocert.DirCache(dataDir),
		}

		httpsSrv.Addr = config.Host + ":443"
		httpsSrv.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}

		// Spin up web server on port 80 to listen for autocert HTTP challenge
		httpSrv = makeHTTPServer()
		httpSrv.Addr = ":80"

		// allow autocert handle Let's Encrypt auth callbacks over HTTP.
		if m != nil {
			// https://github.com/golang/go/issues/21890
			httpSrv.Handler = m.HTTPHandler(httpSrv.Handler)
		}

		// Launch HTTP server
		go func() {

			fmt.Println("Starting server http://localhost")

			err := httpSrv.ListenAndServe()
			if err != nil {
				log.Fatalf("httpSrv.ListenAndServe() failed with %s", err)
			}

		}()

		// Launch HTTPS server

		fmt.Println("Starting server https://" + config.Host + ":" + config.Port)
		log.Fatal(httpsSrv.ListenAndServeTLS("", ""))

	}

}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, htmlIndex)
}

func makeServerFromMux(mux *http.ServeMux) *http.Server {
	// set timeouts so that a slow or malicious client doesn't
	// hold resources forever
	return &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 5 * time.Second,
		IdleTimeout:  120 * time.Second,
		Handler:      mux,
	}
}

func makeHTTPServer() *http.Server {
	mux := &http.ServeMux{}
	mux.HandleFunc("/", handleIndex)
	return makeServerFromMux(mux)

}

func initialMigration() {

	// Migrate the schema
	db.AutoMigrate(&Task{})

}

func isJSON(str string) bool {
	var js json.RawMessage
	return json.Unmarshal([]byte(str), &js) == nil
}

func runCommand(cmd string, t Task) string {

	// See https://regexr.com/4154h for custom regex to parse commands
	// Inspired by https://gist.github.com/danesparza/a651ac923d6313b9d1b7563c9245743b
	pattern := `(--[^\s]+="[^"]+")|"([^"]+)"|'([^']+)'|([^\s]+)`
	parts := regexp.MustCompile(pattern).FindAllString(cmd, -1)

	//	The first part is the command, the rest are the args:
	head := parts[0]
	arguments := parts[1:len(parts)]

	//	Format the command
	command := exec.Command(head, arguments...)

	//	Sanity check -- capture stdout and stderr:
	var out bytes.Buffer
	var stderr bytes.Buffer
	command.Stdout = &out    // Standard out: out.String()
	command.Stderr = &stderr // Standard errors: stderr.String()

	//	Run the command
	command.Run()

	t.Status = "Completed"

	// Add results to db if in JSON format
	if isJSON(out.String()) {
		t.Response = out.String()
	}

	db.Save(&t)

	return out.String()

}

func serverCmd() *cobra.Command {
	return &cobra.Command{
		Use: "server",
		RunE: func(cmd *cobra.Command, args []string) error {

			// Handle Subsequent requests
			handleRequests()

			return nil
		},
	}
}

func checkSecurity(next httpHandlerFunc) httpHandlerFunc {
	return func(res http.ResponseWriter, req *http.Request) {
		header := req.Header.Get("token")
		if header != config.Token {
			res.WriteHeader(http.StatusUnauthorized)
			res.Write([]byte("401 - Unauthorized"))
			return
		}
		next(res, req)
	}
}

// main function to boot up everything
func main() {

	db, err = gorm.Open("sqlite3", "sql.db")
	if err != nil {
		panic("failed to connect database")
	}
	defer db.Close()

	initialMigration()

	cmd := &cobra.Command{
		Use:     "captaincore-dispatch",
		Short:   "CaptainCore Dispatch Server 💻",
		Version: "0.1",
	}

	cmd.SetUsageTemplate(`Usage:{{if .Runnable}}
  {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
  {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}

Aliases:
  {{.NameAndAliases}}{{end}}{{if .HasExample}}

Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}

Available Commands: {{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
  {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}

Flags: 
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}

Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}

Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}

Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`)

	cmd.AddCommand(serverCmd())

	if err := cmd.Execute(); err != nil {
		//fmt.Println(err)
		os.Exit(0)
	}

}

References