mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
e527e2ab77
With this PR, Electron will generate a new authorization key that the Go backend will look for in any incoming requests. The Electron backend will inject this header with all requests to the backend to ensure no additional work is required on the frontend. This also adds a `fetchutil` abstraction that will use the Electron `net` module when calls are made from the Electron backend to the Go backend. When using the `node:fetch` module, Electron can't inject headers to requests. The Electron `net` module is also faster than the Node module. This also breaks out platform functions in emain into their own file so other emain modules can import them.
259 lines
6.8 KiB
Go
259 lines
6.8 KiB
Go
// Copyright 2024, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"runtime/debug"
|
|
"strconv"
|
|
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/wavetermdev/thenextwave/pkg/authkey"
|
|
"github.com/wavetermdev/thenextwave/pkg/filestore"
|
|
"github.com/wavetermdev/thenextwave/pkg/service"
|
|
"github.com/wavetermdev/thenextwave/pkg/telemetry"
|
|
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
|
|
"github.com/wavetermdev/thenextwave/pkg/wavebase"
|
|
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
|
"github.com/wavetermdev/thenextwave/pkg/wcloud"
|
|
"github.com/wavetermdev/thenextwave/pkg/wconfig"
|
|
"github.com/wavetermdev/thenextwave/pkg/web"
|
|
"github.com/wavetermdev/thenextwave/pkg/wps"
|
|
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
|
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshremote"
|
|
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshserver"
|
|
"github.com/wavetermdev/thenextwave/pkg/wshutil"
|
|
"github.com/wavetermdev/thenextwave/pkg/wstore"
|
|
)
|
|
|
|
// these are set at build time
|
|
var WaveVersion = "0.0.0"
|
|
var BuildTime = "0"
|
|
|
|
const InitialTelemetryWait = 30 * time.Second
|
|
const TelemetryTick = 10 * time.Minute
|
|
const TelemetryInterval = 4 * time.Hour
|
|
|
|
const ReadySignalPidVarName = "WAVETERM_READY_SIGNAL_PID"
|
|
|
|
var shutdownOnce sync.Once
|
|
|
|
func doShutdown(reason string) {
|
|
shutdownOnce.Do(func() {
|
|
log.Printf("shutting down: %s\n", reason)
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancelFn()
|
|
shutdownActivityUpdate()
|
|
sendTelemetryWrapper()
|
|
// TODO deal with flush in progress
|
|
filestore.WFS.FlushCache(ctx)
|
|
watcher := wconfig.GetWatcher()
|
|
if watcher != nil {
|
|
watcher.Close()
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
os.Exit(0)
|
|
})
|
|
}
|
|
|
|
func installShutdownSignalHandlers() {
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
|
|
go func() {
|
|
for sig := range sigCh {
|
|
doShutdown(fmt.Sprintf("got signal %v", sig))
|
|
break
|
|
}
|
|
}()
|
|
}
|
|
|
|
// watch stdin, kill server if stdin is closed
|
|
func stdinReadWatch() {
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
_, err := os.Stdin.Read(buf)
|
|
if err != nil {
|
|
doShutdown(fmt.Sprintf("stdin closed/error (%v)", err))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func configWatcher() {
|
|
watcher := wconfig.GetWatcher()
|
|
if watcher != nil {
|
|
watcher.Start()
|
|
}
|
|
}
|
|
|
|
func telemetryLoop() {
|
|
var nextSend int64
|
|
time.Sleep(InitialTelemetryWait)
|
|
for {
|
|
if time.Now().Unix() > nextSend {
|
|
nextSend = time.Now().Add(TelemetryInterval).Unix()
|
|
sendTelemetryWrapper()
|
|
}
|
|
time.Sleep(TelemetryTick)
|
|
}
|
|
}
|
|
|
|
func sendTelemetryWrapper() {
|
|
defer func() {
|
|
r := recover()
|
|
if r == nil {
|
|
return
|
|
}
|
|
log.Printf("[error] in sendTelemetryWrapper: %v\n", r)
|
|
debug.PrintStack()
|
|
}()
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancelFn()
|
|
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
|
|
if err != nil {
|
|
log.Printf("[error] getting client data for telemetry: %v\n", err)
|
|
return
|
|
}
|
|
err = wcloud.SendTelemetry(ctx, client.OID)
|
|
if err != nil {
|
|
log.Printf("[error] sending telemetry: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func startupActivityUpdate() {
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancelFn()
|
|
activity := telemetry.ActivityUpdate{
|
|
Startup: 1,
|
|
}
|
|
activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx)
|
|
err := telemetry.UpdateActivity(ctx, activity) // set at least one record into activity (don't use go routine wrap here)
|
|
if err != nil {
|
|
log.Printf("error updating startup activity: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func shutdownActivityUpdate() {
|
|
activity := telemetry.ActivityUpdate{Shutdown: 1}
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 1*time.Second)
|
|
defer cancelFn()
|
|
err := telemetry.UpdateActivity(ctx, activity) // do NOT use the go routine wrap here (this needs to be synchronous)
|
|
if err != nil {
|
|
log.Printf("error updating shutdown activity: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func createMainWshClient() {
|
|
rpc := wshserver.GetMainRpcClient()
|
|
wshutil.DefaultRouter.RegisterRoute(wshutil.DefaultRoute, rpc)
|
|
wps.Broker.SetClient(wshutil.DefaultRouter)
|
|
localConnWsh := wshutil.MakeWshRpc(nil, nil, wshrpc.RpcContext{}, &wshremote.ServerImpl{})
|
|
wshutil.DefaultRouter.RegisterRoute(wshutil.MakeConnectionRouteId(wshrpc.LocalConnName), localConnWsh)
|
|
}
|
|
|
|
func main() {
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
|
log.SetPrefix("[wavesrv] ")
|
|
wavebase.WaveVersion = WaveVersion
|
|
wavebase.BuildTime = BuildTime
|
|
|
|
err := authkey.SetAuthKeyFromEnv()
|
|
if err != nil {
|
|
log.Printf("error setting auth key: %v\n", err)
|
|
return
|
|
}
|
|
|
|
err = service.ValidateServiceMap()
|
|
if err != nil {
|
|
log.Printf("error validating service map: %v\n", err)
|
|
return
|
|
}
|
|
err = wavebase.EnsureWaveHomeDir()
|
|
if err != nil {
|
|
log.Printf("error ensuring wave home dir: %v\n", err)
|
|
return
|
|
}
|
|
waveLock, err := wavebase.AcquireWaveLock()
|
|
if err != nil {
|
|
log.Printf("error acquiring wave lock (another instance of Wave is likely running): %v\n", err)
|
|
return
|
|
}
|
|
defer func() {
|
|
err = waveLock.Unlock()
|
|
if err != nil {
|
|
log.Printf("error releasing wave lock: %v\n", err)
|
|
}
|
|
}()
|
|
|
|
log.Printf("wave version: %s (%s)\n", WaveVersion, BuildTime)
|
|
log.Printf("wave home dir: %s\n", wavebase.GetWaveHomeDir())
|
|
err = filestore.InitFilestore()
|
|
if err != nil {
|
|
log.Printf("error initializing filestore: %v\n", err)
|
|
return
|
|
}
|
|
err = wstore.InitWStore()
|
|
if err != nil {
|
|
log.Printf("error initializing wstore: %v\n", err)
|
|
return
|
|
}
|
|
go func() {
|
|
err := shellutil.InitCustomShellStartupFiles()
|
|
if err != nil {
|
|
log.Printf("error initializing wsh and shell-integration files: %v\n", err)
|
|
}
|
|
}()
|
|
err = wstore.EnsureInitialData()
|
|
if err != nil {
|
|
log.Printf("error ensuring initial data: %v\n", err)
|
|
return
|
|
}
|
|
createMainWshClient()
|
|
installShutdownSignalHandlers()
|
|
startupActivityUpdate()
|
|
go stdinReadWatch()
|
|
go telemetryLoop()
|
|
configWatcher()
|
|
webListener, err := web.MakeTCPListener("web")
|
|
if err != nil {
|
|
log.Printf("error creating web listener: %v\n", err)
|
|
return
|
|
}
|
|
wsListener, err := web.MakeTCPListener("websocket")
|
|
if err != nil {
|
|
log.Printf("error creating websocket listener: %v\n", err)
|
|
return
|
|
}
|
|
go web.RunWebSocketServer(wsListener)
|
|
unixListener, err := web.MakeUnixListener()
|
|
if err != nil {
|
|
log.Printf("error creating unix listener: %v\n", err)
|
|
return
|
|
}
|
|
go func() {
|
|
pidStr := os.Getenv(ReadySignalPidVarName)
|
|
if pidStr != "" {
|
|
_, err := strconv.Atoi(pidStr)
|
|
if err == nil {
|
|
if BuildTime == "" {
|
|
BuildTime = "0"
|
|
}
|
|
// use fmt instead of log here to make sure it goes directly to stderr
|
|
fmt.Fprintf(os.Stderr, "WAVESRV-ESTART ws:%s web:%s version:%s buildtime:%s\n", wsListener.Addr(), webListener.Addr(), WaveVersion, BuildTime)
|
|
}
|
|
}
|
|
}()
|
|
go wshutil.RunWshRpcOverListener(unixListener)
|
|
web.RunWebServer(webListener) // blocking
|
|
runtime.KeepAlive(waveLock)
|
|
}
|