a cloud service to enable your own web server (owned by you and running on your computer) to be accessible on the internet in seconds, no credit card required
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

229 lines
5.5 KiB

package main
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path"
"time"
errors "git.sequentialread.com/forest/pkg-errors"
base58 "github.com/shengdoushi/base58"
)
type IngressService struct {
BaseHTTPService
Model *DBModel
DaemonIsRunning bool
AdminTenantId int
FrontendPort int
}
type GUITunnel struct {
Protocol string `json:"protocol"`
HasSubdomain bool `json:"has_subdomain"`
Subdomain string `json:"subdomain"`
Domain string `json:"domain"`
DestinationType string `json:"destination_type"`
DestinationPort int `json:"destination_port"`
}
type GreenhouseDaemonStatus struct {
NeedsAPIToken bool `json:"needs_api_token"`
}
const adminThresholdNodeId = "greenhouse_internal_node"
func NewIngressService(config *Config, model *DBModel) *IngressService {
toReturn := &IngressService{
Model: model,
AdminTenantId: config.AdminTenantId,
FrontendPort: config.FrontendPort,
}
toReturn.ClientFactory = func() (*http.Client, *time.Time, error) {
// 99 years aka forever, never expire
expiryTime := time.Now().Add(time.Hour * 24 * 30 * 12 * 99)
return &http.Client{
Transport: CreateUnixTransport("/var/run/greenhouse-daemon.sock"),
Timeout: 10 * time.Second,
}, &expiryTime, nil
}
return toReturn
}
func (service *IngressService) StartGreenhouseDaemon() error {
if service.DaemonIsRunning {
return errors.New("Daemon is already Running ")
}
cwd, err := os.Getwd()
if err != nil {
return err
}
greenhouseDaemonPath := path.Join(cwd, "greenhouse-daemon")
command := exec.Command(path.Join(greenhouseDaemonPath, "greenhouse-daemon"))
env := os.Environ()
env = append(env, fmt.Sprintf("GREENHOUSE_DAEMON_PATH=%s", greenhouseDaemonPath))
env = append(env, "GREENHOUSE_DAEMON_USE_UNIX_SOCKETS=true")
env = append(env, fmt.Sprintf("GREENHOUSE_DAEMON_CLOUD_URL=http://localhost:%d", service.FrontendPort))
command.Env = env
command.Dir = greenhouseDaemonPath
stdoutReader, err := command.StdoutPipe()
if err != nil {
log.Printf("can't Start greenhouse-daemon, command.StdoutPipe() returned %s", err)
return err
}
stdoutScanner := bufio.NewScanner(stdoutReader)
stdoutScanner.Split(bufio.ScanLines)
go (func() {
for stdoutScanner.Scan() {
log.Printf("[greenhouse-daemon stdout] %s \n", stdoutScanner.Text())
}
})()
stderrReader, err := command.StderrPipe()
if err != nil {
log.Printf("can't Start(greenhouse-daemon), command.StdoutPipe() returned %s", err)
return err
}
stderrScanner := bufio.NewScanner(stderrReader)
stderrScanner.Split(bufio.ScanLines)
go (func() {
for stderrScanner.Scan() {
log.Printf("[greenhouse-daemon stderr] %s \n", stderrScanner.Text())
}
})()
err = command.Start()
if err != nil {
log.Printf("can't Start greenhouse-daemon, command.Start() returned %s", err)
return err
}
log.Printf("started greenhouse-daemon with PID %d\n", command.Process.Pid)
service.DaemonIsRunning = true
go (func() {
err := command.Wait()
service.DaemonIsRunning = false
if err != nil {
log.Printf("command.Wait() returned '%s' for greenhouse-daemon child process", err)
}
log.Printf("greenhouse-daemon child process ended with exit code %d", command.ProcessState.ExitCode())
})()
return nil
}
func (service *IngressService) GetGreenhouseDaemonStatus() (string, error) {
responseBytes, err := service.MyHTTP200("GET", "http://unix/status", nil, nil)
if err != nil {
return "", err
}
return string(responseBytes), nil
}
func (service *IngressService) ConfigureGreenhouseDaemon() error {
responseBytes, err := service.MyHTTP200("GET", "http://unix/status", nil, nil)
if err != nil {
return err
}
var responseStatus GreenhouseDaemonStatus
err = json.Unmarshal(responseBytes, &responseStatus)
if err != nil {
return err
}
tenant, err := service.Model.GetTenant(service.AdminTenantId)
if err != nil {
return err
}
if responseStatus.NeedsAPIToken {
i := 0
newTokenName := "greenhouse_builtin_ingress"
for i < 100 {
conflict := false
for _, token := range tenant.APITokens {
if token.Name == newTokenName {
conflict = true
}
}
if conflict == false {
break
}
i++
newTokenName = fmt.Sprintf("greenhouse_builtin_ingress_%d", i)
}
if i >= 100 {
return errors.New("too many greenhouse_builtin_ingress tokens")
}
apiTokenBuffer := make([]byte, 16)
rand.Read(apiTokenBuffer)
apiToken := base58.Encode(apiTokenBuffer, base58.BitcoinAlphabet)
rawHash := sha256.Sum256([]byte(apiToken))
hashedAPIToken := fmt.Sprintf("%x", rawHash)
err = service.Model.CreateAPIToken(service.AdminTenantId, newTokenName, hashedAPIToken)
if err != nil {
return err
}
_, err := service.MyHTTP200(
"POST",
fmt.Sprintf("http://unix/register?serverName=%s", adminThresholdNodeId),
nil,
func(request *http.Request) {
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiToken))
},
)
if err != nil {
return err
}
}
greenhouseGUITunnels := []GUITunnel{
GUITunnel{
Protocol: "https",
HasSubdomain: true,
Subdomain: "greenhouse",
Domain: fmt.Sprintf("%s.%s", tenant.Subdomain, freeSubdomainDomain),
DestinationType: "local_port",
DestinationPort: service.FrontendPort,
},
}
tunnelsBytes, err := json.Marshal(greenhouseGUITunnels)
if err != nil {
return err
}
_, err = service.MyHTTP200(
"POST",
"http://unix/apply_config",
bytes.NewBuffer(tunnelsBytes),
nil,
)
if err != nil {
return err
}
return nil
}