func (svc *Service) Apply(ctx context.Context, request *protob.ApplyRequest) (*protob.ApplyResponse, error) {
	if svc.HTTP == nil {
		svc.HTTP = http.DefaultClient
	}
	if svc.Addr == "" {
		svc.Addr = "http://127.0.0.1:2019"
	}
	var siteRoutes, nodeRoutes, nodeAltRoutes []caddy.ServerRoute
	for k, site := range request.GetSites() {
		hosts := []string{site.GetHostname()}
		if site.GetAliases() != "" {
			hosts = append(hosts, strings.Split(site.GetAliases(), ",")...)
		}
		siteRoutes = append(siteRoutes, caddy.ServerRoute{
			Handle: []caddy.RouteHandle{
				{
					Handler: "reverse_proxy",
					Upstreams: []caddy.Upstream{
						{
							Dial: fmt.Sprintf("%s:%d", k, site.GetPort()),
						},
					},
				},
			},
			Match: []caddy.Match{
				{
					Host: hosts,
				},
			},
			Terminal: true,
		})
		nodeRoutes = append(nodeRoutes, caddy.ServerRoute{
			Handle: []caddy.RouteHandle{
				{
					Handler: "reverse_proxy",
					Upstreams: []caddy.Upstream{
						{
							Dial: fmt.Sprintf("%s:%d", k, 3000),
						},
					},
				},
			},
			Match: []caddy.Match{
				{
					Host: hosts,
				},
			},
			Terminal: true,
		})
		nodeAltRoutes = append(nodeAltRoutes, caddy.ServerRoute{
			Handle: []caddy.RouteHandle{
				{
					Handler: "reverse_proxy",
					Upstreams: []caddy.Upstream{
						{
							Dial: fmt.Sprintf("%s:%d", k, 3001),
						},
					},
				},
			},
			Match: []caddy.Match{
				{
					Host: hosts,
				},
			},
			Terminal: true,
		})
	}
	update := caddy.UpdateRequest{}
	httpRoutes := append(siteRoutes, caddy.ServerRoute{
		Handle: []caddy.RouteHandle{
			{
				Handler: "vars",
				Root:    "/var/www/html",
			},
			{
				Handler: "file_server",
				Root:    "/var/www/html",
				Hide:    []string{"/etc/caddy/Caddyfile"},
			},
		},
		Terminal: true,
	})
	update.Node = caddy.Server{
		Listen: []string{":3000"},
		Routes: nodeRoutes,
		AutomaticHTTPS: caddy.AutomaticHTTPS{
			Disable:          true,
			DisableRedirects: true,
		},
	}
	update.NodeAlt = caddy.Server{
		Listen: []string{":3001"},
		Routes: nodeAltRoutes,
		AutomaticHTTPS: caddy.AutomaticHTTPS{
			Disable:          true,
			DisableRedirects: true,
		},
	}
	update.HTTP = caddy.Server{
		Listen: []string{":80"},
		Routes: httpRoutes,
		AutomaticHTTPS: caddy.AutomaticHTTPS{
			DisableRedirects: true,
		},
	}
	update.HTTPS = caddy.Server{
		Listen: []string{":443"},
		Routes: siteRoutes,
	}
	content, err := json.Marshal(&update)
	if err != nil {
		return nil, err
	}
	res, err := svc.HTTP.Post(svc.Addr+"/config/apps/http/servers", "application/json", bytes.NewReader(content))
	if err != nil {
		return &protob.ApplyResponse{
			Message: fmt.Sprintf("Error updating Caddy API, err: %s", err.Error()),
			Error:   true,
		}, err
	}
	if res.StatusCode != http.StatusOK {
		return &protob.ApplyResponse{
			Message: fmt.Sprintf("Received %d response from Caddy API", res.StatusCode),
			Error:   true,
		}, nil
	}
	return &protob.ApplyResponse{
		Message: fmt.Sprintf("Successfully applied changes, sites: %d", len(request.GetSites())),
		Error:   false,
	}, nil
}
Apply is used to take all of the sites from a Nitro config and apply those changes. The Sites
in protob.ApplyRequest represents the hostname, aliases (in a comma delimited list), and the
port for the service. The NGINX container type uses port 8080 and the PHP-FPM container type
uses port 9000.
package api
import (
	"archive/zip"
	"bytes"
	"compress/gzip"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"github.com/craftcms/nitro/pkg/caddy"
	"github.com/craftcms/nitro/pkg/database"
	"github.com/craftcms/nitro/pkg/portavail"
	"github.com/craftcms/nitro/protob"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)
var Version string
func NewService(addr string) protob.NitroServer {
	if env, ok := os.LookupEnv("NITRO_VERSION"); ok {
		Version = env
	}
	return &Service{
		Addr:     addr,
		HTTP:     http.DefaultClient,
		Importer: database.NewImporter(),
	}
}
type Service struct {
	Addr     string
	HTTP     *http.Client
	Importer database.Importer
}
func (svc *Service) AddDatabase(ctx context.Context, req *protob.AddDatabaseRequest) (*protob.AddDatabaseResponse, error) {
	hostname := req.GetDatabase().GetHostname()
	port := req.GetDatabase().GetPort()
	engine := req.GetDatabase().GetEngine()
	version := req.GetDatabase().GetVersion()
	db := req.GetDatabase().GetDatabase()
	if err := portavail.Check(hostname, port); err == nil {
		return nil, status.Errorf(codes.Internal, "it does not appear the database is available on host %s using port %s: %v", hostname, port, err)
	}
	tool, err := database.DefaultImportToolFinder(engine, version)
	if err != nil {
		return nil, status.Error(codes.Internal, "error finding the database tool")
	}
	var addCommand, privilegesCommand []string
	switch engine {
	case "mysql":
		addCommand = []string{"--user=nitro", fmt.Sprintf("--host=%s", hostname), "-pnitro", fmt.Sprintf(`-e CREATE DATABASE IF NOT EXISTS %s;`, db)}
		privilegesCommand = []string{"--user=nitro", fmt.Sprintf("--host=%s", hostname), "-pnitro", fmt.Sprintf(`-e CREATE DATABASE IF NOT EXISTS %s;`, db)}
	default:
		addCommand = []string{fmt.Sprintf("--host=%s", hostname), "--port=" + port, "--username=nitro", fmt.Sprintf(`-c CREATE DATABASE %s;`, db)}
	}
	if err := svc.exec(tool, addCommand); err != nil {
		return nil, status.Error(codes.Internal, fmt.Sprintf("error creating database: %s", err.Error()))
	}
	if privilegesCommand != nil {
		if err := svc.exec(tool, addCommand); err != nil {
			return nil, status.Error(codes.Internal, fmt.Sprintf("error setting privileges on database: %s", err.Error()))
		}
	}
	return &protob.AddDatabaseResponse{Message: fmt.Sprintf("Database %q added to %q successfully", db, hostname)}, nil
}