-
Notifications
You must be signed in to change notification settings - Fork 230
/
daemon.go
160 lines (146 loc) · 4.46 KB
/
daemon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package cmdutil
import (
"context"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"encr.dev/internal/version"
"encr.dev/pkg/xos"
daemonpb "encr.dev/proto/encore/daemon"
)
// ConnectDaemon sets up the Encore daemon if it isn't already running
// and returns a client connected to it.
func ConnectDaemon(ctx context.Context) daemonpb.DaemonClient {
socketPath, err := daemonSockPath()
if err != nil {
fmt.Fprintln(os.Stderr, "fatal: ", err)
os.Exit(1)
}
if _, err := xos.SocketStat(socketPath); err == nil {
// The socket exists; check that it is responsive.
if cc, err := dialDaemon(ctx, socketPath); err == nil {
// Make sure the daemon is running an up-to-date version;
// restart it otherwise.
cl := daemonpb.NewDaemonClient(cc)
if resp, err := cl.Version(ctx, &empty.Empty{}); err == nil {
diff := version.Compare(resp.Version)
switch {
case diff < 0:
// Daemon is running a newer version
return cl
case diff == 0:
if configHash, err := version.ConfigHash(); err != nil {
Fatal("unable to get config path: ", err)
} else if configHash == resp.ConfigHash {
return cl
}
// Daemon is running the same version but different config
fmt.Fprintf(os.Stderr, "encore: restarting daemon due to configuration change.\n")
case diff > 0:
fmt.Fprintf(os.Stderr, "encore: daemon is running an outdated version (%s), restarting.\n", resp.Version)
}
}
}
// Remove the socket file which triggers the daemon to exit.
_ = os.Remove(socketPath)
}
// Start the daemon.
if err := StartDaemonInBackground(ctx); err != nil {
Fatal("starting daemon: ", err)
}
cc, err := dialDaemon(ctx, socketPath)
if err != nil {
Fatal("dialing daemon: ", err)
}
return daemonpb.NewDaemonClient(cc)
}
func StopDaemon() {
socketPath, err := daemonSockPath()
if err != nil {
Fatal("stopping daemon: ", err)
}
if _, err := xos.SocketStat(socketPath); err == nil {
_ = os.Remove(socketPath)
}
}
// daemonSockPath reports the path to the Encore daemon unix socket.
func daemonSockPath() (string, error) {
cacheDir, err := os.UserCacheDir()
if err != nil {
return "", fmt.Errorf("could not determine cache dir: %v", err)
}
return filepath.Join(cacheDir, "encore", "encored.sock"), nil
}
// StartDaemonInBackground starts the Encore daemon in the background.
func StartDaemonInBackground(ctx context.Context) error {
socketPath, err := daemonSockPath()
if err != nil {
return err
}
// nosemgrep
exe, err := os.Executable()
if err != nil {
exe, err = exec.LookPath("encore")
}
if err != nil {
return fmt.Errorf("could not determine location of encore executable: %v", err)
}
// nosemgrep
cmd := exec.Command(exe, "daemon", "-f")
cmd.SysProcAttr = xos.CreateNewProcessGroup()
if err := cmd.Start(); err != nil {
return fmt.Errorf("could not start encore daemon: %v", err)
}
// Wait for it to come up
for i := 0; i < 50; i++ {
if err := ctx.Err(); err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
if _, err := xos.SocketStat(socketPath); err == nil {
return nil
}
}
return fmt.Errorf("timed out waiting for daemon to start")
}
func dialDaemon(ctx context.Context, socketPath string) (*grpc.ClientConn, error) {
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
defer cancel()
dialer := func(ctx context.Context, addr string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "unix", socketPath)
}
return grpc.DialContext(ctx, "",
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithUnaryInterceptor(errInterceptor),
grpc.WithContextDialer(dialer))
}
func errInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
err := invoker(ctx, method, req, reply, cc, opts...)
if err != nil {
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Unauthenticated {
Fatal("not logged in: run 'encore auth login' first")
}
for _, detail := range st.Details() {
switch t := detail.(type) {
case *errdetails.PreconditionFailure:
for _, violation := range t.Violations {
if violation.Type == "INVALID_REFRESH_TOKEN" {
Fatal("OAuth refresh token was invalid. Please run `encore auth login` again.")
}
}
}
}
}
}
return err
}