/ pkg / web / rest.go
rest.go
  1  package web
  2  
  3  import (
  4  	"context"
  5  	"encoding/gob"
  6  	"fmt"
  7  	"log"
  8  	"net/http"
  9  	"os"
 10  
 11  	dogeboxd "github.com/dogeorg/dogeboxd/pkg"
 12  	"github.com/dogeorg/dogeboxd/pkg/conductor"
 13  	"github.com/rs/cors"
 14  )
 15  
 16  func RESTAPI(
 17  	config dogeboxd.ServerConfig,
 18  	sm dogeboxd.StateManager,
 19  	dbx dogeboxd.Dogeboxd,
 20  	pups dogeboxd.PupManager,
 21  	sources dogeboxd.SourceManager,
 22  	lifecycle dogeboxd.LifecycleManager,
 23  	nix dogeboxd.NixManager,
 24  	dkm dogeboxd.DKMManager,
 25  	ws WSRelay,
 26  ) conductor.Service {
 27  	sessions = []Session{}
 28  
 29  	if config.DevMode {
 30  		log.Println("In development mode: Loading REST API sessions..")
 31  		file, err := os.Open(fmt.Sprintf("%s/dev-sessions.gob", config.DataDir))
 32  		if err == nil {
 33  			decoder := gob.NewDecoder(file)
 34  			err = decoder.Decode(&sessions)
 35  			if err != nil {
 36  				log.Printf("Failed to decode sessions from dev-sessions.gob: %v", err)
 37  			}
 38  			file.Close()
 39  			log.Printf("Loaded %d sessions from dev-sessions.gob", len(sessions))
 40  		} else {
 41  			log.Printf("Failed to open dev-sessions.gob: %v", err)
 42  		}
 43  	}
 44  
 45  	a := api{
 46  		mux:       http.NewServeMux(),
 47  		config:    config,
 48  		sm:        sm,
 49  		dbx:       dbx,
 50  		pups:      pups,
 51  		ws:        ws,
 52  		dkm:       dkm,
 53  		lifecycle: lifecycle,
 54  		nix:       nix,
 55  		sources:   sources,
 56  	}
 57  
 58  	routes := map[string]http.HandlerFunc{}
 59  
 60  	// Recovery routes are the _only_ routes loaded in recovery mode.
 61  	recoveryRoutes := map[string]http.HandlerFunc{
 62  		"POST /authenticate": a.authenticate,
 63  		"POST /logout":       a.logout,
 64  
 65  		"GET /system/bootstrap": a.getBootstrap,
 66  		"GET /system/keymaps":   a.getKeymaps,
 67  		"POST /system/keymap":   a.setKeyMap,
 68  		"GET /system/disks":     a.getInstallDisks,
 69  		"POST /system/hostname": a.setHostname,
 70  		"POST /system/storage":  a.setStorageDevice,
 71  		"POST /system/install":  a.installToDisk,
 72  
 73  		"GET /system/network/list":        a.getNetwork,
 74  		"PUT /system/network/set-pending": a.setPendingNetwork,
 75  		"POST /system/network/connect":    a.connectNetwork,
 76  		"POST /system/host/shutdown":      a.hostShutdown,
 77  		"POST /system/host/reboot":        a.hostReboot,
 78  		"POST /keys/create-master":        a.createMasterKey,
 79  		"GET /keys":                       a.listKeys,
 80  		"POST /system/bootstrap":          a.initialBootstrap,
 81  
 82  		"GET /system/ssh/state":       a.getSSHState,
 83  		"PUT /system/ssh/state":       a.setSSHState,
 84  		"GET /system/ssh/keys":        a.listSSHKeys,
 85  		"PUT /system/ssh/key":         a.addSSHKey,
 86  		"DELETE /system/ssh/key/{id}": a.removeSSHKey,
 87  	}
 88  
 89  	// Normal routes are used when we are not in recovery mode.
 90  	// nb. These are used in _addition_ to recovery routes.
 91  	normalRoutes := map[string]http.HandlerFunc{
 92  		"GET /pup/{ID}/metrics":   a.getPupMetrics,
 93  		"POST /pup/{ID}/{action}": a.pupAction,
 94  		"PUT /pup":                a.installPup,
 95  		"POST /config/{PupID}":    a.updateConfig,
 96  		"POST /providers/{PupID}": a.updateProviders,
 97  		"GET /providers/{PupID}":  a.getPupProviders,
 98  		"POST /hooks/{PupID}":     a.updateHooks,
 99  		"GET /sources":            a.getSources,
100  		"PUT /source":             a.createSource,
101  		"GET /sources/store":      a.getStoreList,
102  		"DELETE /source/{id}":     a.deleteSource,
103  		"/ws/log/{PupID}":         a.getLogSocket,
104  		"/ws/state/":              a.getUpdateSocket,
105  	}
106  
107  	// We always want to load recovery routes.
108  	for k, v := range recoveryRoutes {
109  		routes[k] = v
110  	}
111  
112  	// If we're not in recovery mode, also load our normal routes.
113  	if !config.Recovery {
114  		for k, v := range normalRoutes {
115  			routes[k] = v
116  		}
117  		log.Printf("Loaded %d API routes", len(routes))
118  	} else {
119  		log.Printf("In recovery mode: Loading limited routes")
120  	}
121  
122  	for p, h := range routes {
123  		a.mux.HandleFunc(p, authReq(dbx, sm, p, h))
124  	}
125  
126  	return a
127  }
128  
129  type api struct {
130  	dbx       dogeboxd.Dogeboxd
131  	sm        dogeboxd.StateManager
132  	dkm       dogeboxd.DKMManager
133  	mux       *http.ServeMux
134  	pups      dogeboxd.PupManager
135  	config    dogeboxd.ServerConfig
136  	sources   dogeboxd.SourceManager
137  	lifecycle dogeboxd.LifecycleManager
138  	nix       dogeboxd.NixManager
139  	ws        WSRelay
140  }
141  
142  func (t api) Run(started, stopped chan bool, stop chan context.Context) error {
143  	go func() {
144  		handler := cors.AllowAll().Handler(t.mux)
145  		srv := &http.Server{Addr: fmt.Sprintf("%s:%d", t.config.Bind, t.config.Port), Handler: handler}
146  		go func() {
147  			if err := srv.ListenAndServe(); err != http.ErrServerClosed {
148  				log.Fatalf("HTTP server public ListenAndServe: %v", err)
149  			}
150  		}()
151  
152  		started <- true
153  		ctx := <-stop
154  		srv.Shutdown(ctx)
155  		stopped <- true
156  	}()
157  	return nil
158  }