mirror of
https://github.com/vale981/agent
synced 2025-03-04 09:01:42 -05:00
control RESTful API added
This commit is contained in:
parent
f8855ca56a
commit
51691334b3
10 changed files with 885 additions and 464 deletions
192
README.md
192
README.md
|
@ -41,6 +41,198 @@ To get usage of all parameters just run `indihub-agent -help`.
|
|||
|
||||
The latest `indihub-agent` release can be downloaded from [releases](https://github.com/indihub-space/agent/releases) or [indihub.space](https://indihub.space) Web-site.
|
||||
|
||||
## API
|
||||
|
||||
There is an API-server running as part of `indihub-agent` and listening on port `:2020` (or on port specified via `-api-port=N` parameter) which provides two different APIs to control or use `indihub-agent`:
|
||||
|
||||
- RESTful API to get status or switch modes of the the agent
|
||||
- Websocket API to control equipment via websocket-connections
|
||||
|
||||
By default API-server works over HTTP-protocol. You can switch it to work over TLS by providing `-api-tls` parameter. This will make `indihub-agent` to generate self-signed CA and certificate.
|
||||
|
||||
### RESTful API
|
||||
|
||||
You can use this simple RESTful API to control `indihub-agent`.
|
||||
|
||||
NOTE: CORS protection can be specified with comma-separated list of allowed origins via agent parameter:
|
||||
|
||||
`-api-origins=host1,host2,hostN`.
|
||||
|
||||
Also, all examples assume `indihub-agent` is running on host `raspberrypi.local`.
|
||||
|
||||
#### 1. Get indihub-agent status (public)
|
||||
|
||||
`curl -X GET http://raspberrypi.local:2020/status`
|
||||
|
||||
Response example:
|
||||
```json
|
||||
{
|
||||
"indiProfile": "NEO-remote",
|
||||
"indiServer": "raspberrypi.local:7624",
|
||||
"mode": "solo",
|
||||
"phd2Server": "",
|
||||
"status": "running",
|
||||
"supportedModes": [
|
||||
"solo",
|
||||
"share",
|
||||
"robotic"
|
||||
],
|
||||
"version": "1.0.3"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Restart indihub-agent current mode (public)
|
||||
|
||||
`curl -X GET "http://raspberrypi.local:2020/status"`
|
||||
|
||||
#### 3. Switch indihub-agent mode (protected via token)
|
||||
|
||||
You need to do HTTP-request `POST "http://indihub-agent-host:2020/mode/{mode}"` specifying required mode in a `mode` URI-parameter and supplying your token in `Authorization` header, i.e.:
|
||||
|
||||
`curl -X POST "http://raspberrypi.local:2020/mode/solo" -H "Authorization: Bearer cca13ac2951efd6d912ead20a7ab4882"`
|
||||
|
||||
Response will have status of agent running in new mode:
|
||||
|
||||
```json
|
||||
{
|
||||
"indiProfile": "NEO-remote",
|
||||
"indiServer": "raspberrypi.local:7624",
|
||||
"mode": "share",
|
||||
"phd2Server": "",
|
||||
"publicEndpoints": [
|
||||
{
|
||||
"name": "INDI-Server",
|
||||
"addr": "node-1.indihub.io:55642"
|
||||
}
|
||||
],
|
||||
"status": "running",
|
||||
"supportedModes": [
|
||||
"share",
|
||||
"robotic",
|
||||
"solo"
|
||||
],
|
||||
"version": "1.0.3"
|
||||
}
|
||||
```
|
||||
|
||||
### Websocket API
|
||||
|
||||
You can use `indihub-agent` to control your equipment via Websocket API, i.e. from your Web-app open int the Web-browser.
|
||||
|
||||
NOTE: Websocket upgrade-requests are allowed only from origins specified with comma-separated list via agent parameter:
|
||||
|
||||
`-api-origins=host1,host2,hostN`
|
||||
|
||||
#### 1. Open WS-connection to INDI-server (protected via token)
|
||||
|
||||
To establish new WS-connection to INDI-server you will need to use URL with format:
|
||||
|
||||
`ws://raspberrypi.local:2020/websocket/indiserver?token=cca13ac2951efd6d912ead20a7ab4882`
|
||||
|
||||
NOTE: we connect to `raspberrypi.local:2020` which is `indihub-agent` API-server and we provide token via `token` query string parameter.
|
||||
|
||||
This will open WS-connection to you your equipment via `indihub-agent` API-server where all outgoing messages will be INDI-protocol commands and all incoming messages will be INDI-protocol replies from your equipment.
|
||||
|
||||
#### 2. Message format for INDI-server Websocket connection
|
||||
|
||||
All commands are expected to be in JSON-format where INDI-command XML attributes translate to fields with `attr_`-prefix.
|
||||
|
||||
I.e. to send this INDI XML-command over WS-connection:
|
||||
|
||||
```xml
|
||||
<getProperties version="1.7" />
|
||||
```
|
||||
|
||||
You will need to send message over WS-connection in JSON-format:
|
||||
```json
|
||||
{
|
||||
"getProperties": {
|
||||
"attr_version":"1.7"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The INDI-protocol responses will be converted from XML to JSON-messages and sent over WS-connection as messages.
|
||||
|
||||
I.e. this INDI-response about telescopes installed on the mount in XML-format:
|
||||
```xml
|
||||
<defNumberVector device="iOptron CEM25" name="TELESCOPE_INFO" label="Scope Properties" group="Options" state="Ok" perm="rw" timeout="60" timestamp="2020-02-20T21:52:23">
|
||||
<defNumber name="TELESCOPE_APERTURE" label="Aperture (mm)" format="%g" min="10" max="5000" step="0">
|
||||
120
|
||||
</defNumber>
|
||||
<defNumber name="TELESCOPE_FOCAL_LENGTH" label="Focal Length (mm)" format="%g" min="10" max="10000" step="0">
|
||||
600
|
||||
</defNumber>
|
||||
<defNumber name="GUIDER_APERTURE" label="Guider Aperture (mm)" format="%g" min="10" max="5000" step="0">
|
||||
50
|
||||
</defNumber>
|
||||
<defNumber name="GUIDER_FOCAL_LENGTH" label="Guider Focal Length (mm)" format="%g" min="10" max="10000" step="0">
|
||||
162
|
||||
</defNumber>
|
||||
</defNumberVector>
|
||||
```
|
||||
|
||||
Will be converted in to JSON-format like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"defNumberVector": {
|
||||
"attr_device":"iOptron CEM25",
|
||||
"attr_group":"Options",
|
||||
"attr_label":"Scope Properties",
|
||||
"attr_name":"TELESCOPE_INFO",
|
||||
"attr_perm":"rw",
|
||||
"attr_state":"Ok",
|
||||
"attr_timeout":60,
|
||||
"attr_timestamp":"2020-02-20T21:52:23",
|
||||
"defNumber": [
|
||||
{
|
||||
"#text":120,
|
||||
"attr_format":"%g",
|
||||
"attr_label":"Aperture (mm)",
|
||||
"attr_max":5000,
|
||||
"attr_min":10,
|
||||
"attr_name":"TELESCOPE_APERTURE",
|
||||
"attr_step":0
|
||||
},
|
||||
{
|
||||
"#text":600,
|
||||
"attr_format":"%g",
|
||||
"attr_label":"Focal Length (mm)",
|
||||
"attr_max":10000,
|
||||
"attr_min":10,
|
||||
"attr_name":"TELESCOPE_FOCAL_LENGTH",
|
||||
"attr_step":0
|
||||
},
|
||||
{
|
||||
"#text":50,
|
||||
"attr_format":"%g",
|
||||
"attr_label":"Guider Aperture (mm)",
|
||||
"attr_max":5000,
|
||||
"attr_min":10,
|
||||
"attr_name":"GUIDER_APERTURE",
|
||||
"attr_step":0
|
||||
},
|
||||
{
|
||||
"#text":162,
|
||||
"attr_format":"%g",
|
||||
"attr_label":"Guider Focal Length (mm)",
|
||||
"attr_max":10000,
|
||||
"attr_min":10,
|
||||
"attr_name":"GUIDER_FOCAL_LENGTH",
|
||||
"attr_step":0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Please NOTE:
|
||||
|
||||
- XML element attributes get converted into JSON-fields with `attr_` prefix
|
||||
- XML element value get converted into JSON-field with name `#text`
|
||||
- vector like child-elements get converted into JSON-arrays
|
||||
|
||||
## Building indihub-agent
|
||||
|
||||
You will need to install [Golang](https://golang.org/dl/).
|
||||
|
|
363
apiserver/server.go
Normal file
363
apiserver/server.go
Normal file
|
@ -0,0 +1,363 @@
|
|||
package apiserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/indihub-space/agent/version"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
elog "github.com/labstack/gommon/log"
|
||||
|
||||
"github.com/indihub-space/agent/lib"
|
||||
"github.com/indihub-space/agent/logutil"
|
||||
)
|
||||
|
||||
var allowedOrigins = map[string]bool{
|
||||
"indihub.space": true,
|
||||
"app.indihub.space": true,
|
||||
"kids.indihub.space": true,
|
||||
}
|
||||
|
||||
// AgentMode provides interface to operate with agent from API-server
|
||||
type AgentMode interface {
|
||||
Start()
|
||||
Stop()
|
||||
GetStatus() map[string]interface{}
|
||||
}
|
||||
|
||||
type APIServer struct {
|
||||
token string
|
||||
indiServerAddr string
|
||||
phd2ServerAddr string
|
||||
port uint64
|
||||
isTLS bool
|
||||
origins string
|
||||
|
||||
e *echo.Echo
|
||||
upgrader websocket.Upgrader
|
||||
connList []net.Conn
|
||||
|
||||
indiProfile string
|
||||
currMode string
|
||||
agentModes map[string]AgentMode
|
||||
}
|
||||
|
||||
func NewAPIServer(token string, indiServerAddr string, phd2ServerAddr string, port uint64, isTLS bool, origins string,
|
||||
currMode string, indiProfile string, agentModes map[string]AgentMode) *APIServer {
|
||||
|
||||
apiServer := &APIServer{
|
||||
token: token,
|
||||
indiServerAddr: indiServerAddr,
|
||||
phd2ServerAddr: phd2ServerAddr,
|
||||
port: port,
|
||||
isTLS: isTLS,
|
||||
e: echo.New(),
|
||||
upgrader: websocket.Upgrader{},
|
||||
connList: []net.Conn{},
|
||||
indiProfile: indiProfile,
|
||||
currMode: currMode,
|
||||
agentModes: agentModes,
|
||||
}
|
||||
|
||||
if logutil.IsDev {
|
||||
allowedOrigins["localhost"] = true
|
||||
}
|
||||
|
||||
// add optional additional origins
|
||||
for _, orig := range strings.Split(origins, ",") {
|
||||
allowedOrigins[strings.TrimSpace(orig)] = true
|
||||
}
|
||||
|
||||
// allow WS connections only from number of domains
|
||||
apiServer.upgrader.CheckOrigin = func(r *http.Request) bool {
|
||||
origin := r.Header["Origin"]
|
||||
if len(origin) == 0 {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(origin[0])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
host, _, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return allowedOrigins[host]
|
||||
}
|
||||
|
||||
return apiServer
|
||||
}
|
||||
|
||||
func (s *APIServer) newIndiConnection(c echo.Context) error {
|
||||
// upgrade to WS connection
|
||||
ws, err := s.upgrader.Upgrade(c.Response(), c.Request(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
// open connection to INDI-Server
|
||||
conn, err := net.Dial("tcp", s.indiServerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add to connection list
|
||||
s.connList = append(s.connList, conn)
|
||||
|
||||
// read messages from INDI-server and write them to WS
|
||||
go func(indiConn net.Conn, wsConn *websocket.Conn) {
|
||||
buf := make([]byte, lib.INDIServerMaxSendMsgSize, lib.INDIServerMaxSendMsgSize)
|
||||
xmlFlattener := lib.NewXmlFlattener()
|
||||
for {
|
||||
// read from INDI-server
|
||||
n, err := indiConn.Read(buf)
|
||||
if err != nil {
|
||||
indiConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
jsonMessages := xmlFlattener.ConvertChunkToJSON(buf[:n])
|
||||
|
||||
// Write to WS
|
||||
for _, m := range jsonMessages {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, m)
|
||||
if err != nil {
|
||||
indiConn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(conn, ws)
|
||||
|
||||
// read messages from WS and write them to INDI-server
|
||||
xmlFlattener := lib.NewXmlFlattener()
|
||||
for {
|
||||
// Read from WS
|
||||
_, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
xmlMsg, err := xmlFlattener.ConvertJSONToXML(msg)
|
||||
if err != nil {
|
||||
log.Printf("could not convert json '%s' to xml: %s", string(msg), err)
|
||||
continue
|
||||
}
|
||||
|
||||
// write to INDI server
|
||||
_, err = conn.Write(xmlMsg)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIServer) newPHD2Connection(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getRestart(c echo.Context) error {
|
||||
// restart current mode
|
||||
if curAgentMode, ok := s.agentModes[s.currMode]; ok {
|
||||
curAgentMode.Stop()
|
||||
time.Sleep(1 * time.Second)
|
||||
curAgentMode.Start()
|
||||
}
|
||||
|
||||
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIServer) getStatus(c echo.Context) error {
|
||||
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIServer) changeMode(c echo.Context) error {
|
||||
newMode := c.Param("new_mode")
|
||||
|
||||
// don't do anything if agent is laready in this mode
|
||||
if newMode == s.currMode {
|
||||
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||
return nil
|
||||
}
|
||||
|
||||
// get agent new mode
|
||||
newAgentMode, ok := s.agentModes[newMode]
|
||||
if !ok {
|
||||
c.JSON(
|
||||
http.StatusBadRequest,
|
||||
map[string]interface{}{
|
||||
"message": "unknown indihub-agent mode: " + newMode,
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// stop current mode
|
||||
if curAgentMode, ok := s.agentModes[s.currMode]; ok {
|
||||
curAgentMode.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// start agent in new mode
|
||||
s.currMode = newMode
|
||||
newAgentMode.Start()
|
||||
|
||||
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *APIServer) agentStatus() map[string]interface{} {
|
||||
agentStatus := map[string]interface{}{
|
||||
"version": version.AgentVersion,
|
||||
"mode": s.currMode,
|
||||
"indiProfile": s.indiProfile,
|
||||
"indiServer": s.indiServerAddr,
|
||||
"phd2Server": s.phd2ServerAddr,
|
||||
}
|
||||
|
||||
supportedModes := make([]string, 0, len(s.agentModes))
|
||||
for key := range s.agentModes {
|
||||
supportedModes = append(supportedModes, key)
|
||||
}
|
||||
agentStatus["supportedModes"] = supportedModes
|
||||
|
||||
if agentMode, ok := s.agentModes[s.currMode]; ok {
|
||||
for key, val := range agentMode.GetStatus() {
|
||||
agentStatus[key] = val
|
||||
}
|
||||
}
|
||||
|
||||
return agentStatus
|
||||
}
|
||||
|
||||
func (s *APIServer) Start() {
|
||||
s.e.HideBanner = true
|
||||
s.e.HidePort = true
|
||||
|
||||
if !logutil.IsDev {
|
||||
s.e.Logger.SetLevel(elog.OFF)
|
||||
}
|
||||
|
||||
// setup middle-wares
|
||||
|
||||
s.e.Use(middleware.Recover())
|
||||
|
||||
// set CORS in case browser decides to do pre-flight OPTIONS request
|
||||
// default ones
|
||||
allowOrigins := []string{}
|
||||
for orig := range allowedOrigins {
|
||||
allowOrigins = append(allowOrigins, "http://"+orig)
|
||||
allowOrigins = append(allowOrigins, "https://"+orig)
|
||||
}
|
||||
// optional ones
|
||||
for _, orig := range strings.Split(s.origins, ",") {
|
||||
allowOrigins = append(allowOrigins, "http://"+strings.TrimSpace(orig))
|
||||
allowOrigins = append(allowOrigins, "https://"+strings.TrimSpace(orig))
|
||||
}
|
||||
// add localhost for dev-mode
|
||||
if logutil.IsDev {
|
||||
allowOrigins = append(allowOrigins, "http://localhost:5000")
|
||||
}
|
||||
s.e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: allowOrigins,
|
||||
AllowMethods: []string{
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
},
|
||||
}))
|
||||
|
||||
// setup routing for WS and RESTful APIs
|
||||
|
||||
// protected WS-API
|
||||
wsGroup := s.e.Group(
|
||||
"/websocket",
|
||||
// set auth middleware
|
||||
middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
|
||||
KeyLookup: "query:token",
|
||||
Validator: func(token string, eCtx echo.Context) (b bool, err error) {
|
||||
return token == s.token, nil
|
||||
},
|
||||
}),
|
||||
)
|
||||
wsGroup.GET("/indiserver", s.newIndiConnection)
|
||||
wsGroup.GET("/phd2server", s.newPHD2Connection)
|
||||
|
||||
// protected RESTful API
|
||||
s.e.POST(
|
||||
"/mode/:new_mode",
|
||||
s.changeMode,
|
||||
// set auth middleware
|
||||
middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
|
||||
KeyLookup: "header:Authorization",
|
||||
Validator: func(token string, eCtx echo.Context) (b bool, err error) {
|
||||
return token == s.token, nil
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
// public RESTful API
|
||||
s.e.GET("/status", s.getStatus)
|
||||
s.e.GET("/restart", s.getRestart)
|
||||
|
||||
// start agent in a required mode
|
||||
agentMode, ok := s.agentModes[s.currMode]
|
||||
if !ok {
|
||||
log.Println("unknown agent mode:", s.currMode)
|
||||
return
|
||||
}
|
||||
agentMode.Start()
|
||||
|
||||
// check if we are running TLS
|
||||
if s.isTLS {
|
||||
// generate self-signed cert to serve WS over TLS
|
||||
keyFile, certFile, err := getSelfSignedCert()
|
||||
if err != nil {
|
||||
log.Println("could not start API-server, self-signed certificate generating failed:", err)
|
||||
return
|
||||
}
|
||||
// start HTTP/WS API server over TLS
|
||||
err = s.e.StartTLS(fmt.Sprintf(":%d", s.port), certFile, keyFile)
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
log.Println("API-server error:", err)
|
||||
} else {
|
||||
log.Println("API-server was shutdown gracefully")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// start HTTP/WS API server
|
||||
err := s.e.Start(fmt.Sprintf(":%d", s.port))
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
log.Println("API-server error:", err)
|
||||
} else {
|
||||
log.Println("API-server was shutdown gracefully")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *APIServer) Stop() {
|
||||
if agentMode, ok := s.agentModes[s.currMode]; ok {
|
||||
agentMode.Stop()
|
||||
}
|
||||
for _, conn := range s.connList {
|
||||
conn.Close()
|
||||
}
|
||||
s.e.Shutdown(context.Background())
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package websockets
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
|
@ -6,4 +6,8 @@ const (
|
|||
|
||||
INDIServerMaxRecvMsgSize = 49152
|
||||
INDIServerMaxSendMsgSize = 2048
|
||||
|
||||
ModeSolo = "solo"
|
||||
ModeShare = "share"
|
||||
ModeRobotic = "robotic"
|
||||
)
|
||||
|
|
291
main.go
291
main.go
|
@ -11,32 +11,25 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/indihub-space/agent/apiserver"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
_ "google.golang.org/grpc/encoding/gzip"
|
||||
|
||||
"github.com/indihub-space/agent/config"
|
||||
"github.com/indihub-space/agent/hostutils"
|
||||
"github.com/indihub-space/agent/lib"
|
||||
"github.com/indihub-space/agent/logutil"
|
||||
"github.com/indihub-space/agent/manager"
|
||||
"github.com/indihub-space/agent/proto/indihub"
|
||||
"github.com/indihub-space/agent/proxy"
|
||||
"github.com/indihub-space/agent/share"
|
||||
"github.com/indihub-space/agent/solo"
|
||||
"github.com/indihub-space/agent/version"
|
||||
"github.com/indihub-space/agent/websockets"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultWSPort uint64 = 2020
|
||||
|
||||
modeSolo = "solo"
|
||||
modeShare = "share"
|
||||
modeRobotic = "robotic"
|
||||
defaultAPIPort uint64 = 2020
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -47,10 +40,9 @@ var (
|
|||
flagConfFile string
|
||||
flagSoloINDIServerAddr string
|
||||
flagCompress bool
|
||||
flagWSServer bool
|
||||
flagWSIsTLS bool
|
||||
flagWSPort uint64
|
||||
flagWSOrigins string
|
||||
flagAPITLS bool
|
||||
flagAPIPort uint64
|
||||
flagAPIOrigins string
|
||||
flagMode string
|
||||
|
||||
indiServerAddr string
|
||||
|
@ -68,7 +60,7 @@ func init() {
|
|||
flag.StringVar(
|
||||
&flagMode,
|
||||
"mode",
|
||||
modeSolo,
|
||||
lib.ModeSolo,
|
||||
`indihub-agent mode (deafult value is "solo"), there four modes:\n
|
||||
solo - equipment sharing is not possible, you are connected to INDIHUB and contributing images
|
||||
share - you are sharing equipment with another INDIHUB user (agent will output connection info)
|
||||
|
@ -112,35 +104,29 @@ robotic - equipment sharing is not possible, your equipment is controlled by IND
|
|||
"Name of INDI-profile to share via indihub",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&flagWSServer,
|
||||
"ws-server",
|
||||
true,
|
||||
"launch Websocket server to control equipment via Websocket API",
|
||||
)
|
||||
flag.BoolVar(
|
||||
&flagWSIsTLS,
|
||||
"ws-tls",
|
||||
&flagAPITLS,
|
||||
"api-tls",
|
||||
false,
|
||||
"serve web-socket over TLS with self-signed certificate",
|
||||
"serve API-server over TLS with self-signed certificate",
|
||||
)
|
||||
flag.Uint64Var(
|
||||
&flagWSPort,
|
||||
"ws-port",
|
||||
defaultWSPort,
|
||||
"port to start web socket-server on",
|
||||
&flagAPIPort,
|
||||
"api-port",
|
||||
defaultAPIPort,
|
||||
"port to start API-server on",
|
||||
)
|
||||
flag.StringVar(
|
||||
&flagWSOrigins,
|
||||
"ws-origins",
|
||||
&flagAPIOrigins,
|
||||
"api-origins",
|
||||
"",
|
||||
"comma-separated list of origins allowed to connect to WS-server",
|
||||
"comma-separated list of origins allowed to connect to API-server",
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if flagMode != modeSolo && flagMode != modeShare && flagMode != modeRobotic {
|
||||
if flagMode != lib.ModeSolo && flagMode != lib.ModeShare && flagMode != lib.ModeRobotic {
|
||||
log.Fatalf("Unknown mode '%s' provided\n", flagMode)
|
||||
}
|
||||
|
||||
|
@ -239,9 +225,9 @@ func main() {
|
|||
Autoconnect: indiProfile.AutoConnect,
|
||||
},
|
||||
Drivers: make([]*indihub.INDIDriver, len(indiDrivers)),
|
||||
SoloMode: flagMode == modeSolo,
|
||||
SoloMode: flagMode == lib.ModeSolo,
|
||||
IsPHD2: flagPHD2ServerAddr != "",
|
||||
IsRobotic: flagMode == modeRobotic,
|
||||
IsRobotic: flagMode == lib.ModeRobotic,
|
||||
IsBroadcast: false,
|
||||
AgentVersion: version.AgentVersion,
|
||||
Os: runtime.GOOS,
|
||||
|
@ -287,6 +273,8 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
log.Println("...OK")
|
||||
// close grpc client connection at the very end
|
||||
defer conn.Close()
|
||||
|
||||
indiHubClient := indihub.NewINDIHubClient(conn)
|
||||
|
||||
|
@ -296,21 +284,7 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Current agent version:", version.AgentVersion)
|
||||
log.Println("Latest agent version:", regInfo.AgentVersion)
|
||||
|
||||
if version.AgentVersion < regInfo.AgentVersion {
|
||||
yc := color.New(color.FgYellow)
|
||||
yc.Println()
|
||||
yc.Println(" ************************************************************")
|
||||
yc.Println(" * WARNING: you version of agent is outdated! *")
|
||||
yc.Println(" * *")
|
||||
yc.Println(" * Please download the latest version from: *")
|
||||
yc.Println(" * https://indihub.space/downloads *")
|
||||
yc.Println(" * *")
|
||||
yc.Println(" ************************************************************")
|
||||
yc.Println(" ")
|
||||
}
|
||||
version.CheckAgentVersion(regInfo.AgentVersion)
|
||||
|
||||
log.Printf("Access token: %s\n", regInfo.Token)
|
||||
log.Printf("Host session token: %s\n", regInfo.SessionIDPublic)
|
||||
|
@ -325,203 +299,40 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// start WS-server
|
||||
wsServer := websockets.NewWsServer(
|
||||
// prepare all modes
|
||||
soloMode := solo.NewMode(indiHubClient, regInfo, indiServerAddr, ccdDrivers)
|
||||
shareMode := share.NewMode(indiHubClient, regInfo, indiServerAddr, flagPHD2ServerAddr, lib.ModeShare)
|
||||
roboticMode := share.NewMode(indiHubClient, regInfo, indiServerAddr, flagPHD2ServerAddr, lib.ModeRobotic)
|
||||
|
||||
// start API-server
|
||||
apiServer := apiserver.NewAPIServer(
|
||||
regInfo.Token,
|
||||
indiServerAddr,
|
||||
flagPHD2ServerAddr,
|
||||
flagWSPort,
|
||||
flagWSIsTLS,
|
||||
flagWSOrigins,
|
||||
flagAPIPort,
|
||||
flagAPITLS,
|
||||
flagAPIOrigins,
|
||||
flagMode,
|
||||
flagINDIProfile,
|
||||
map[string]apiserver.AgentMode{
|
||||
lib.ModeSolo: soloMode,
|
||||
lib.ModeShare: shareMode,
|
||||
lib.ModeRobotic: roboticMode,
|
||||
},
|
||||
)
|
||||
go wsServer.Start()
|
||||
|
||||
// start session
|
||||
switch flagMode {
|
||||
go func() {
|
||||
sigint := make(chan os.Signal, 1)
|
||||
signal.Notify(sigint, os.Interrupt, os.Kill)
|
||||
|
||||
case modeSolo:
|
||||
// solo mode - equipment sharing is not available but host still sends all images to INDIHUB
|
||||
log.Println("'solo' parameter was provided. Your session is in solo-mode: equipment sharing is not available")
|
||||
log.Println("Starting INDIHUB agent in solo mode!")
|
||||
<-sigint
|
||||
|
||||
soloClient, err := indiHubClient.SoloMode(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start agent in solo mode: %v", err)
|
||||
}
|
||||
log.Println("Stopping API-server gracefully")
|
||||
|
||||
soloAgent := solo.New(
|
||||
indiServerAddr,
|
||||
soloClient,
|
||||
ccdDrivers,
|
||||
)
|
||||
// close connections to local INDI-server
|
||||
apiServer.Stop()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
sigint := make(chan os.Signal, 1)
|
||||
signal.Notify(sigint, os.Interrupt, os.Kill)
|
||||
|
||||
<-sigint
|
||||
|
||||
// stop WS-server
|
||||
wsServer.Stop()
|
||||
|
||||
log.Println("Closing INDIHUB solo-session")
|
||||
|
||||
// close connections to local INDI-server and to INDI client
|
||||
soloAgent.Close()
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// close grpc client connection
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
// start solo mode INDI-server tcp-proxy
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
soloAgent.Start(regInfo.SessionID, regInfo.SessionIDPublic)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
case modeShare, modeRobotic:
|
||||
// main equipment sharing mode
|
||||
if flagMode == modeRobotic {
|
||||
log.Println("'robotic' parameter was provided. Your session is in robotic-mode: equipment sharing is not available")
|
||||
}
|
||||
// open INDI server tunnel
|
||||
log.Println("Starting INDI-Server in the cloud...")
|
||||
indiServTunnel, err := indiHubClient.INDIServer(
|
||||
context.Background(),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("...OK")
|
||||
|
||||
indiFilterConf := &hostutils.INDIFilterConfig{} // TODO: add reading config
|
||||
indiFilter := hostutils.NewINDIFilter(indiFilterConf)
|
||||
indiServerProxy := proxy.New("INDI-Server", indiServerAddr, indiServTunnel, indiFilter)
|
||||
|
||||
// start PHD2 server proxy if specified
|
||||
var phd2ServerProxy *proxy.TcpProxy
|
||||
if flagPHD2ServerAddr != "" {
|
||||
// open PHD2 server tunnel
|
||||
log.Println("Starting PHD2-Server in the cloud...")
|
||||
phd2ServTunnel, err := indiHubClient.PHD2Server(
|
||||
context.Background(),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("...OK")
|
||||
phd2ServerProxy = proxy.New("PHD2-Server", flagPHD2ServerAddr, phd2ServTunnel, nil)
|
||||
}
|
||||
|
||||
go func() {
|
||||
sigint := make(chan os.Signal, 1)
|
||||
signal.Notify(sigint, os.Interrupt, os.Kill)
|
||||
|
||||
<-sigint
|
||||
|
||||
// stop WS-server
|
||||
wsServer.Stop()
|
||||
|
||||
// close connections to tunnels
|
||||
indiServTunnel.CloseSend()
|
||||
if phd2ServerProxy != nil {
|
||||
phd2ServerProxy.Tunnel.CloseSend()
|
||||
}
|
||||
|
||||
// close grpc client connection
|
||||
conn.Close()
|
||||
|
||||
// close connections to local INDI-server and PHD2-Server
|
||||
indiServerProxy.Close()
|
||||
if phd2ServerProxy != nil {
|
||||
phd2ServerProxy.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
serverAddrChan := make(chan proxy.PublicServerAddr, 3)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
// INDI Server Proxy start
|
||||
waitNum := 1
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
indiServerProxy.Start(serverAddrChan, regInfo.SessionID, regInfo.SessionIDPublic)
|
||||
}()
|
||||
|
||||
if flagPHD2ServerAddr != "" {
|
||||
waitNum = 2
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
phd2ServerProxy.Start(serverAddrChan, regInfo.SessionID, regInfo.SessionIDPublic)
|
||||
}()
|
||||
}
|
||||
|
||||
addrData := []proxy.PublicServerAddr{}
|
||||
for i := 0; i < waitNum; i++ {
|
||||
sAddr := <-serverAddrChan
|
||||
addrData = append(addrData, sAddr)
|
||||
}
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
gc := color.New(color.FgGreen)
|
||||
yc := color.New(color.FgYellow)
|
||||
rc := color.New(color.FgMagenta)
|
||||
if flagMode != modeRobotic {
|
||||
c.Println()
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" * INDIHUB public address list!! *")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" ")
|
||||
for _, sAddr := range addrData {
|
||||
gc.Printf(" %s: %s\n", sAddr.Name, sAddr.Addr)
|
||||
}
|
||||
c.Println(" ")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println()
|
||||
c.Println(" Please provide your guest with this information:")
|
||||
c.Println()
|
||||
c.Println(" 1. Public address list from the above")
|
||||
c.Println(" 2. Focal length and aperture of your main telescope")
|
||||
c.Println(" 3. Focal length and aperture of your guiding telescope")
|
||||
c.Println(" 4. Type of guiding you use: PHD2 or guiding via camera")
|
||||
c.Println(" 5. Names of your imaging camera and guiding cameras")
|
||||
c.Println()
|
||||
yc.Println(" NOTE: These public addresses will be available ONLY until")
|
||||
yc.Println(" agent is running! (Ctrl+C will stop the session)")
|
||||
c.Println()
|
||||
} else {
|
||||
c.Println()
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" * INDIHUB robotic-session started!! *")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" ")
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
c.Println()
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" * INDIHUB session finished!! *")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" ")
|
||||
if flagMode != modeRobotic {
|
||||
for _, sAddr := range addrData {
|
||||
rc.Printf(" %s: %s - CLOSED!!\n", sAddr.Name, sAddr.Addr)
|
||||
}
|
||||
} else {
|
||||
c.Println(" * INDIHUB robotic-session finished. *")
|
||||
c.Println(" * Thank you for your contribution! *")
|
||||
}
|
||||
c.Println(" ")
|
||||
c.Println(" ************************************************************")
|
||||
}
|
||||
// start API-server and indihub-agent in the current mode
|
||||
apiServer.Start()
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ type TcpProxy struct {
|
|||
}
|
||||
|
||||
type PublicServerAddr struct {
|
||||
Name string
|
||||
Addr string
|
||||
Name string `json:"name"`
|
||||
Addr string `json:"addr"`
|
||||
}
|
||||
|
||||
func New(name string, addr string, tunnel INDIHubTunnel, filter *hostutils.INDIFilter) *TcpProxy {
|
||||
|
|
178
share/mode.go
Normal file
178
share/mode.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/indihub-space/agent/hostutils"
|
||||
"github.com/indihub-space/agent/lib"
|
||||
"github.com/indihub-space/agent/proto/indihub"
|
||||
"github.com/indihub-space/agent/proxy"
|
||||
)
|
||||
|
||||
type Mode struct {
|
||||
indiHubClient indihub.INDIHubClient
|
||||
regInfo *indihub.RegisterInfo
|
||||
|
||||
indiServerAddr string
|
||||
phd2ServerAddr string
|
||||
|
||||
addrData []proxy.PublicServerAddr
|
||||
|
||||
stopCh chan struct{}
|
||||
status string
|
||||
mode string
|
||||
}
|
||||
|
||||
func NewMode(indiHubClient indihub.INDIHubClient, regInfo *indihub.RegisterInfo, indiServerAddr string, phd2ServerAddr string, mode string) *Mode {
|
||||
return &Mode{
|
||||
indiHubClient: indiHubClient,
|
||||
regInfo: regInfo,
|
||||
indiServerAddr: indiServerAddr,
|
||||
phd2ServerAddr: phd2ServerAddr,
|
||||
mode: mode,
|
||||
addrData: []proxy.PublicServerAddr{},
|
||||
stopCh: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mode) Start() {
|
||||
// main equipment sharing mode
|
||||
if m.mode == lib.ModeRobotic {
|
||||
log.Println("'robotic' parameter was provided. Your session is in robotic-mode: equipment sharing is not available")
|
||||
}
|
||||
// open INDI server tunnel
|
||||
log.Println("Starting INDI-Server in the cloud...")
|
||||
indiServTunnel, err := m.indiHubClient.INDIServer(
|
||||
context.Background(),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("...OK")
|
||||
|
||||
indiFilterConf := &hostutils.INDIFilterConfig{} // TODO: add reading config
|
||||
indiFilter := hostutils.NewINDIFilter(indiFilterConf)
|
||||
indiServerProxy := proxy.New("INDI-Server", m.indiServerAddr, indiServTunnel, indiFilter)
|
||||
|
||||
// start PHD2 server proxy if specified
|
||||
var phd2ServerProxy *proxy.TcpProxy
|
||||
if m.phd2ServerAddr != "" {
|
||||
// open PHD2 server tunnel
|
||||
log.Println("Starting PHD2-Server in the cloud...")
|
||||
phd2ServTunnel, err := m.indiHubClient.PHD2Server(
|
||||
context.Background(),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("...OK")
|
||||
phd2ServerProxy = proxy.New("PHD2-Server", m.phd2ServerAddr, phd2ServTunnel, nil)
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-m.stopCh
|
||||
|
||||
log.Printf("Closing %s-session\n", m.mode)
|
||||
|
||||
// close connections to tunnels
|
||||
indiServTunnel.CloseSend()
|
||||
if phd2ServerProxy != nil {
|
||||
phd2ServerProxy.Tunnel.CloseSend()
|
||||
}
|
||||
|
||||
// close connections to local INDI-server and PHD2-Server
|
||||
indiServerProxy.Close()
|
||||
if phd2ServerProxy != nil {
|
||||
phd2ServerProxy.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
serverAddrChan := make(chan proxy.PublicServerAddr, 3)
|
||||
|
||||
// INDI Server Proxy start
|
||||
go indiServerProxy.Start(serverAddrChan, m.regInfo.SessionID, m.regInfo.SessionIDPublic)
|
||||
sAddr := <-serverAddrChan
|
||||
if m.mode != lib.ModeRobotic {
|
||||
m.addrData = append(m.addrData, sAddr)
|
||||
}
|
||||
|
||||
// PHD2 Server proxy start
|
||||
if m.phd2ServerAddr != "" {
|
||||
go phd2ServerProxy.Start(serverAddrChan, m.regInfo.SessionID, m.regInfo.SessionIDPublic)
|
||||
sAddr := <-serverAddrChan
|
||||
if m.mode != lib.ModeRobotic {
|
||||
m.addrData = append(m.addrData, sAddr)
|
||||
}
|
||||
}
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
gc := color.New(color.FgGreen)
|
||||
yc := color.New(color.FgYellow)
|
||||
if m.mode != lib.ModeRobotic {
|
||||
c.Println()
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" * INDIHUB public address list!! *")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" ")
|
||||
for _, sAddr := range m.addrData {
|
||||
gc.Printf(" %s: %s\n", sAddr.Name, sAddr.Addr)
|
||||
}
|
||||
c.Println(" ")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println()
|
||||
c.Println(" Please provide your guest with this information:")
|
||||
c.Println()
|
||||
c.Println(" 1. Public address list from the above")
|
||||
c.Println(" 2. Focal length and aperture of your main telescope")
|
||||
c.Println(" 3. Focal length and aperture of your guiding telescope")
|
||||
c.Println(" 4. Type of guiding you use: PHD2 or guiding via camera")
|
||||
c.Println(" 5. Names of your imaging camera and guiding cameras")
|
||||
c.Println()
|
||||
yc.Println(" NOTE: These public addresses will be available ONLY until")
|
||||
yc.Println(" agent is running! (Ctrl+C will stop the session)")
|
||||
c.Println()
|
||||
} else {
|
||||
c.Println()
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" * INDIHUB robotic-session started!! *")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" ")
|
||||
}
|
||||
|
||||
m.status = "running"
|
||||
}
|
||||
|
||||
func (m *Mode) Stop() {
|
||||
m.status = "stopped"
|
||||
m.stopCh <- struct{}{}
|
||||
|
||||
c := color.New(color.FgCyan)
|
||||
rc := color.New(color.FgMagenta)
|
||||
c.Println()
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" * INDIHUB session finished!! *")
|
||||
c.Println(" ************************************************************")
|
||||
c.Println(" ")
|
||||
if m.mode != lib.ModeRobotic {
|
||||
for _, sAddr := range m.addrData {
|
||||
rc.Printf(" %s: %s - CLOSED!!\n", sAddr.Name, sAddr.Addr)
|
||||
}
|
||||
} else {
|
||||
c.Println(" * INDIHUB robotic-session finished. *")
|
||||
c.Println(" * Thank you for your contribution! *")
|
||||
}
|
||||
c.Println(" ")
|
||||
c.Println(" ************************************************************")
|
||||
|
||||
m.addrData = []proxy.PublicServerAddr{}
|
||||
}
|
||||
|
||||
func (m *Mode) GetStatus() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"status": m.status,
|
||||
"publicEndpoints": m.addrData,
|
||||
}
|
||||
}
|
70
solo/mode.go
Normal file
70
solo/mode.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package solo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/indihub-space/agent/proto/indihub"
|
||||
)
|
||||
|
||||
type Mode struct {
|
||||
indiServerAddr string
|
||||
indiHubClient indihub.INDIHubClient
|
||||
regInfo *indihub.RegisterInfo
|
||||
ccdDrivers []string
|
||||
|
||||
stopCh chan struct{}
|
||||
status string
|
||||
}
|
||||
|
||||
func NewMode(indiHubClient indihub.INDIHubClient, regInfo *indihub.RegisterInfo, indiServerAddr string, ccdDrivers []string) *Mode {
|
||||
return &Mode{
|
||||
indiServerAddr: indiServerAddr,
|
||||
indiHubClient: indiHubClient,
|
||||
regInfo: regInfo,
|
||||
ccdDrivers: ccdDrivers,
|
||||
stopCh: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Mode) Start() {
|
||||
// solo mode - equipment sharing is not available but host still sends all images to INDIHUB
|
||||
log.Println("'solo' parameter was provided. Your session is in solo-mode: equipment sharing is not available")
|
||||
log.Println("Starting INDIHUB agent in solo mode!")
|
||||
|
||||
soloClient, err := s.indiHubClient.SoloMode(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start agent in solo mode: %v", err)
|
||||
}
|
||||
|
||||
soloAgent := New(
|
||||
s.indiServerAddr,
|
||||
soloClient,
|
||||
s.ccdDrivers,
|
||||
)
|
||||
|
||||
go func() {
|
||||
<-s.stopCh
|
||||
log.Println("Closing INDIHUB solo-session")
|
||||
// close connections to local INDI-server
|
||||
soloAgent.Close()
|
||||
}()
|
||||
|
||||
// start agent in solo-mode
|
||||
go func() {
|
||||
soloAgent.Start(s.regInfo.SessionID, s.regInfo.SessionIDPublic)
|
||||
}()
|
||||
|
||||
s.status = "running"
|
||||
}
|
||||
|
||||
func (s *Mode) Stop() {
|
||||
s.status = "stopped"
|
||||
s.stopCh <- struct{}{}
|
||||
}
|
||||
|
||||
func (s *Mode) GetStatus() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"status": s.status,
|
||||
}
|
||||
}
|
|
@ -1,3 +1,27 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
var AgentVersion = "1.0.2"
|
||||
|
||||
func CheckAgentVersion(latestVer string) {
|
||||
log.Println("Current agent version:", AgentVersion)
|
||||
log.Println("Latest agent version:", latestVer)
|
||||
|
||||
if AgentVersion < latestVer {
|
||||
yc := color.New(color.FgYellow)
|
||||
yc.Println()
|
||||
yc.Println(" ************************************************************")
|
||||
yc.Println(" * WARNING: you version of agent is outdated! *")
|
||||
yc.Println(" * *")
|
||||
yc.Println(" * Please download the latest version from: *")
|
||||
yc.Println(" * https://indihub.space/downloads *")
|
||||
yc.Println(" * *")
|
||||
yc.Println(" ************************************************************")
|
||||
yc.Println(" ")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
package websockets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
elog "github.com/labstack/gommon/log"
|
||||
|
||||
"github.com/indihub-space/agent/lib"
|
||||
"github.com/indihub-space/agent/logutil"
|
||||
)
|
||||
|
||||
var allowedOrigins = map[string]bool{
|
||||
"indihub.space": true,
|
||||
"app.indihub.space": true,
|
||||
"kids.indihub.space": true,
|
||||
}
|
||||
|
||||
type WsServer struct {
|
||||
token string
|
||||
indiServerAddr string
|
||||
phd2ServerAddr string
|
||||
wsPort uint64
|
||||
isTLS bool
|
||||
origins string
|
||||
|
||||
e *echo.Echo
|
||||
upgrader websocket.Upgrader
|
||||
connList []net.Conn
|
||||
}
|
||||
|
||||
func NewWsServer(token string, indiServerAddr string, phd2ServerAddr string, wsPort uint64, isTLS bool, origins string) *WsServer {
|
||||
wsServer := &WsServer{
|
||||
token: token,
|
||||
indiServerAddr: indiServerAddr,
|
||||
phd2ServerAddr: phd2ServerAddr,
|
||||
wsPort: wsPort,
|
||||
isTLS: isTLS,
|
||||
e: echo.New(),
|
||||
upgrader: websocket.Upgrader{},
|
||||
connList: []net.Conn{},
|
||||
}
|
||||
|
||||
if logutil.IsDev {
|
||||
allowedOrigins["localhost"] = true
|
||||
}
|
||||
|
||||
// add optional additional origins
|
||||
for _, orig := range strings.Split(origins, ",") {
|
||||
allowedOrigins[strings.TrimSpace(orig)] = true
|
||||
}
|
||||
|
||||
// allow WS connections only from number of domains
|
||||
wsServer.upgrader.CheckOrigin = func(r *http.Request) bool {
|
||||
origin := r.Header["Origin"]
|
||||
if len(origin) == 0 {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(origin[0])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
host, _, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return allowedOrigins[host]
|
||||
}
|
||||
|
||||
return wsServer
|
||||
}
|
||||
|
||||
func (s *WsServer) newIndiConnection(c echo.Context) error {
|
||||
// upgrade to WS connection
|
||||
ws, err := s.upgrader.Upgrade(c.Response(), c.Request(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
// open connection to INDI-Server
|
||||
conn, err := net.Dial("tcp", s.indiServerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add to connection list
|
||||
s.connList = append(s.connList, conn)
|
||||
|
||||
// read messages from INDI-server and write them to WS
|
||||
go func(indiConn net.Conn, wsConn *websocket.Conn) {
|
||||
buf := make([]byte, lib.INDIServerMaxSendMsgSize, lib.INDIServerMaxSendMsgSize)
|
||||
xmlFlattener := lib.NewXmlFlattener()
|
||||
for {
|
||||
// read from INDI-server
|
||||
n, err := indiConn.Read(buf)
|
||||
if err != nil {
|
||||
indiConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
jsonMessages := xmlFlattener.ConvertChunkToJSON(buf[:n])
|
||||
|
||||
// Write to WS
|
||||
for _, m := range jsonMessages {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, m)
|
||||
if err != nil {
|
||||
indiConn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(conn, ws)
|
||||
|
||||
// read messages from WS and write them to INDI-server
|
||||
xmlFlattener := lib.NewXmlFlattener()
|
||||
for {
|
||||
// Read from WS
|
||||
_, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
xmlMsg, err := xmlFlattener.ConvertJSONToXML(msg)
|
||||
if err != nil {
|
||||
log.Printf("could not convert json '%s' to xml: %s", string(msg), err)
|
||||
continue
|
||||
}
|
||||
|
||||
// write to INDI server
|
||||
_, err = conn.Write(xmlMsg)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WsServer) newPHD2Connection(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WsServer) Start() {
|
||||
s.e.HideBanner = true
|
||||
s.e.HidePort = true
|
||||
|
||||
if !logutil.IsDev {
|
||||
s.e.Logger.SetLevel(elog.OFF)
|
||||
}
|
||||
|
||||
s.e.Use(middleware.Recover())
|
||||
|
||||
// set CORS in case browser decides to do pre-flight OPTIONS request
|
||||
// default ones
|
||||
allowOrigins := []string{}
|
||||
for orig := range allowedOrigins {
|
||||
allowOrigins = append(allowOrigins, "http://"+orig)
|
||||
allowOrigins = append(allowOrigins, "https://"+orig)
|
||||
}
|
||||
// optional ones
|
||||
for _, orig := range strings.Split(s.origins, ",") {
|
||||
allowOrigins = append(allowOrigins, "http://"+strings.TrimSpace(orig))
|
||||
allowOrigins = append(allowOrigins, "https://"+strings.TrimSpace(orig))
|
||||
}
|
||||
// localhost for dev-mode
|
||||
if logutil.IsDev {
|
||||
allowOrigins = append(allowOrigins, "http://localhost:5000")
|
||||
}
|
||||
s.e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: allowOrigins,
|
||||
AllowMethods: []string{http.MethodGet},
|
||||
}))
|
||||
|
||||
// set auth middleware
|
||||
s.e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
|
||||
KeyLookup: "query:token",
|
||||
Validator: func(token string, eCtx echo.Context) (b bool, err error) {
|
||||
return token == s.token, nil
|
||||
},
|
||||
}))
|
||||
|
||||
s.e.GET("/indiserver", s.newIndiConnection)
|
||||
s.e.GET("/phd2server", s.newPHD2Connection)
|
||||
|
||||
// check if we are running TLS
|
||||
if s.isTLS {
|
||||
// generate self-signed cert to serve WS over TLS
|
||||
keyFile, certFile, err := getSelfSignedCert()
|
||||
if err != nil {
|
||||
log.Println("could not start WSS server, cert generation failed:", err)
|
||||
return
|
||||
}
|
||||
err = s.e.StartTLS(fmt.Sprintf(":%d", s.wsPort), certFile, keyFile)
|
||||
if err != nil {
|
||||
log.Println("WSS server error:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// run WS
|
||||
err := s.e.Start(fmt.Sprintf(":%d", s.wsPort))
|
||||
if err != nil {
|
||||
log.Println("WSS server error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WsServer) Stop() {
|
||||
for _, conn := range s.connList {
|
||||
conn.Close()
|
||||
}
|
||||
s.e.Shutdown(context.Background())
|
||||
}
|
Loading…
Add table
Reference in a new issue