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 }