mirror of Walter-Sparrow / lunar-tear
1package main
2
3import (
4 "bufio"
5 "context"
6 "flag"
7 "fmt"
8 "io"
9 "log"
10 "os"
11 "os/exec"
12 "os/signal"
13 "path/filepath"
14 "runtime"
15 "sync"
16 "syscall"
17)
18
19var (
20 colorReset = "\033[0m"
21 colorRed = "\033[31m"
22 colorGreen = "\033[32m"
23 colorYellow = "\033[33m"
24 colorCyan = "\033[36m"
25)
26
27type service struct {
28 label string
29 color string
30 cmd *exec.Cmd
31}
32
33func binExt() string {
34 if runtime.GOOS == "windows" {
35 return ".exe"
36 }
37 return ""
38}
39
40func buildAll() {
41 if err := os.MkdirAll("bin", 0755); err != nil {
42 log.Fatalf("create bin/: %v", err)
43 }
44
45 type target struct {
46 name string
47 pkg string
48 }
49 targets := []target{
50 {"auth-server", "./cmd/auth-server"},
51 {"octo-cdn", "./cmd/octo-cdn"},
52 {"lunar-tear", "./cmd/lunar-tear"},
53 }
54
55 ext := binExt()
56 var wg sync.WaitGroup
57 errs := make(chan error, len(targets))
58
59 for _, t := range targets {
60 wg.Add(1)
61 go func(t target) {
62 defer wg.Done()
63 out := filepath.Join("bin", t.name+ext)
64 cmd := exec.Command("go", "build", "-o", out, t.pkg)
65 cmd.Stdout = os.Stdout
66 cmd.Stderr = os.Stderr
67 if err := cmd.Run(); err != nil {
68 errs <- fmt.Errorf("build %s: %w", t.name, err)
69 }
70 }(t)
71 }
72 wg.Wait()
73 close(errs)
74
75 for err := range errs {
76 log.Fatal(err)
77 }
78}
79
80func main() {
81 // auth-server flags
82 authListen := flag.String("auth.listen", "0.0.0.0:3000", "auth-server listen address (host:port)")
83 authDB := flag.String("auth.db", "db/auth.db", "auth-server SQLite database path")
84
85 // octo-cdn flags
86 cdnListen := flag.String("cdn.listen", "0.0.0.0:8080", "octo-cdn local bind address")
87 cdnPublicAddr := flag.String("cdn.public-addr", "10.0.2.2:8080", "octo-cdn externally-reachable address")
88
89 // lunar-tear (grpc) flags
90 grpcListen := flag.String("grpc.listen", "0.0.0.0:8003", "lunar-tear gRPC listen address (host:port)")
91 grpcPublicAddr := flag.String("grpc.public-addr", "10.0.2.2:8003", "lunar-tear externally-reachable address")
92 grpcDB := flag.String("grpc.db", "db/game.db", "lunar-tear SQLite database path")
93 grpcOctoURL := flag.String("grpc.octo-url", "", "Octo CDN base URL passed to lunar-tear (default: derived from cdn.public-addr)")
94 grpcAuthURL := flag.String("grpc.auth-url", "", "auth server base URL passed to lunar-tear (default: derived from auth.listen)")
95
96 // admin webhook is opt-in; empty leaves lunar-tear's own default in place
97 // (the listener still only binds if LUNAR_ADMIN_TOKEN is set in the env).
98 adminListen := flag.String("admin.listen", "", "lunar-tear admin webhook listen address (host:port). Empty = leave default; webhook only binds when LUNAR_ADMIN_TOKEN is set in the env.")
99
100 // Controlled server access
101 noRegister := flag.Bool("no-register", false, "Disallow new account registrations for clients, when present. Default = false")
102
103 // dev utility output config
104 noColor := flag.Bool("no-color", false, "disable colored output")
105
106 flag.Parse()
107
108 if *grpcOctoURL == "" {
109 *grpcOctoURL = fmt.Sprintf("http://%s", *cdnPublicAddr)
110 }
111 if *grpcAuthURL == "" {
112 *grpcAuthURL = fmt.Sprintf("http://%s", *authListen)
113 }
114
115 if *noColor || !colorSupported() {
116 colorReset = ""
117 colorRed = ""
118 colorGreen = ""
119 colorYellow = ""
120 colorCyan = ""
121 }
122
123 log.Println("building services...")
124 buildAll()
125
126 ext := binExt()
127 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
128 defer stop()
129
130 noreg_s := ""
131 if *noRegister {
132 noreg_s = "--no-register"
133 }
134
135 services := []service{
136 {
137 label: "auth",
138 color: colorGreen,
139 cmd: exec.CommandContext(ctx, filepath.Join("bin", "auth-server"+ext),
140 "--listen", *authListen,
141 "--db", *authDB,
142 noreg_s,
143 ),
144 },
145 {
146 label: "cdn",
147 color: colorCyan,
148 cmd: exec.CommandContext(ctx, filepath.Join("bin", "octo-cdn"+ext),
149 "--listen", *cdnListen,
150 "--public-addr", *cdnPublicAddr,
151 ),
152 },
153 {
154 label: "grpc",
155 color: colorYellow,
156 cmd: exec.CommandContext(ctx, filepath.Join("bin", "lunar-tear"+ext),
157 grpcArgs(*grpcListen, *grpcPublicAddr, *grpcDB, *grpcOctoURL, *grpcAuthURL, *adminListen, *noRegister)...,
158 ),
159 },
160 }
161
162 var wg sync.WaitGroup
163 errCh := make(chan error, len(services))
164
165 for i := range services {
166 svc := &services[i]
167 stdout, err := svc.cmd.StdoutPipe()
168 if err != nil {
169 log.Fatalf("[%s] stdout pipe: %v", svc.label, err)
170 }
171 stderr, err := svc.cmd.StderrPipe()
172 if err != nil {
173 log.Fatalf("[%s] stderr pipe: %v", svc.label, err)
174 }
175
176 if err := svc.cmd.Start(); err != nil {
177 log.Fatalf("[%s] start: %v", svc.label, err)
178 }
179
180 prefix := fmt.Sprintf("%s[%s]%s ", svc.color, svc.label, colorReset)
181 wg.Add(2)
182 go prefixLines(&wg, prefix, stdout)
183 go prefixLines(&wg, prefix, stderr)
184
185 wg.Add(1)
186 go func(s *service) {
187 defer wg.Done()
188 if err := s.cmd.Wait(); err != nil {
189 errCh <- fmt.Errorf("[%s] %w", s.label, err)
190 }
191 }(svc)
192
193 log.Printf("%s%s started (pid %d)%s", svc.color, svc.label, svc.cmd.Process.Pid, colorReset)
194 }
195
196 select {
197 case <-ctx.Done():
198 log.Println("shutting down all services...")
199 case err := <-errCh:
200 log.Printf("%s%s%s", colorRed, err, colorReset)
201 stop()
202 }
203
204 wg.Wait()
205}
206
207func prefixLines(wg *sync.WaitGroup, prefix string, r io.Reader) {
208 defer wg.Done()
209 scanner := bufio.NewScanner(r)
210 for scanner.Scan() {
211 fmt.Printf("%s%s\n", prefix, scanner.Text())
212 }
213}
214
215// grpcArgs assembles the argv for the lunar-tear subprocess. The admin flag
216// is appended only when --admin.listen was supplied so we don't override
217// lunar-tear's own default when the operator hasn't opted in.
218func grpcArgs(listen, publicAddr, db, octoURL, authURL, adminListen string, noRegister bool) []string {
219 args := []string{
220 "--listen", listen,
221 "--public-addr", publicAddr,
222 "--db", db,
223 "--octo-url", octoURL,
224 "--auth-url", authURL,
225 }
226
227 if adminListen != "" {
228 args = append(args, "--admin-listen", adminListen)
229 }
230
231 if noRegister {
232 args = append(args, "--no-register")
233 }
234 return args
235}