-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathroot.go
More file actions
412 lines (351 loc) · 15.7 KB
/
root.go
File metadata and controls
412 lines (351 loc) · 15.7 KB
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
package cmd
import (
"context"
"fmt"
"kubecloud/internal/api/app"
cfg "kubecloud/internal/config"
"kubecloud/internal/infrastructure/logger"
"net"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func addFlags() error {
rootCmd.PersistentFlags().StringP("config", "c", "./config.json", "Path to the configuration file (default: ./config.json)")
if err := viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config")); err != nil {
return fmt.Errorf("failed to bind config flag: %w", err)
}
// === Server ===
if err := bindStringFlag(rootCmd, "server.host", "", "Server host"); err != nil {
return fmt.Errorf("failed to bind server.host flag: %w", err)
}
if err := bindStringFlag(rootCmd, "server.port", "", "Server port"); err != nil {
return fmt.Errorf("failed to bind server.port flag: %w", err)
}
// === Database ===
// DSN-only database configuration (Postgres)
if err := bindStringFlag(rootCmd, "database.dsn", "", "Database DSN"); err != nil {
return fmt.Errorf("failed to bind database.dsn flag: %w", err)
}
// === JWT Token ===
if err := bindStringFlag(rootCmd, "jwt_token.secret", "", "JWT secret"); err != nil {
return fmt.Errorf("failed to bind jwt_token.secret flag: %w", err)
}
if err := bindIntFlag(rootCmd, "jwt_token.access_expiry_minutes", 60, "Access token expiry (minutes)"); err != nil {
return fmt.Errorf("failed to bind jwt_token.access_expiry_minutes flag: %w", err)
}
if err := bindIntFlag(rootCmd, "jwt_token.refresh_expiry_hours", 24, "Refresh token expiry (hours)"); err != nil {
return fmt.Errorf("failed to bind jwt_token.refresh_expiry_hours flag: %w", err)
}
// === Admins ===
if err := bindStringFlag(rootCmd, "admins", "", "Comma-separated list of admin emails"); err != nil {
return fmt.Errorf("failed to bind admins flag: %w", err)
}
// === Mail Sender ===
if err := bindStringFlag(rootCmd, "mailSender.email", "", "Sender email"); err != nil {
return fmt.Errorf("failed to bind mailSender.email flag: %w", err)
}
if err := bindStringFlag(rootCmd, "mailSender.sendgrid_key", "", "SendGrid API key"); err != nil {
return fmt.Errorf("failed to bind mailSender.sendgrid_key flag: %w", err)
}
if err := bindIntFlag(rootCmd, "mailSender.timeout", 5, "Send timeout (minutes)"); err != nil {
return fmt.Errorf("failed to bind mailSender.timeout flag: %w", err)
}
if err := bindIntFlag(rootCmd, "mailSender.max_concurrent_sends", 20, "Max concurrent sends"); err != nil {
return fmt.Errorf("failed to bind mailSender.max_concurrent_sends flag: %w", err)
}
if err := bindIntFlag(rootCmd, "mailSender.max_attachment_size_mb", 10, "Max attachment size (MB)"); err != nil {
return fmt.Errorf("failed to bind mailSender.max_attachment_size_mb flag: %w", err)
}
// === Stripe ===
if err := bindStringFlag(rootCmd, "currency", "", "Currency (e.g., USD)"); err != nil {
return fmt.Errorf("failed to bind currency flag: %w", err)
}
if err := bindStringFlag(rootCmd, "stripe_secret", "", "Stripe secret"); err != nil {
return fmt.Errorf("failed to bind stripe_secret flag: %w", err)
}
// === Voucher ===
if err := bindIntFlag(rootCmd, "voucher_name_length", 6, "Voucher name length"); err != nil {
return fmt.Errorf("failed to bind voucher_name_length flag: %w", err)
}
// === Verification Code ===
if err := bindIntFlag(rootCmd, "verification_code_length", 4, "Verification code length"); err != nil {
return fmt.Errorf("failed to bind verification_code_length flag: %w", err)
}
// === Terms and Conditions ===
if err := bindStringFlag(rootCmd, "terms_and_conditions.document_link", "", "Terms document link"); err != nil {
return fmt.Errorf("failed to bind terms_and_conditions.document_link flag: %w", err)
}
if err := bindStringFlag(rootCmd, "terms_and_conditions.document_hash", "", "Terms document hash"); err != nil {
return fmt.Errorf("failed to bind terms_and_conditions.document_hash flag: %w", err)
}
// === System Account ===
if err := bindStringFlag(rootCmd, "system_account.mnemonic", "", "System account mnemonic"); err != nil {
return fmt.Errorf("failed to bind system_account.mnemonic flag: %w", err)
}
if err := bindStringFlag(rootCmd, "system_account.network", "", "System account network"); err != nil {
return fmt.Errorf("failed to bind system_account.network flag: %w", err)
}
// === Grid ===
if err := bindStringFlag(rootCmd, "grid.mnemonic", "", "Grid mnemonic"); err != nil {
return fmt.Errorf("failed to bind grid.mnemonic flag: %w", err)
}
if err := bindStringFlag(rootCmd, "grid.net", "", "Grid network"); err != nil {
return fmt.Errorf("failed to bind grid.net flag: %w", err)
}
// === Deployer Workers ===
if err := bindIntFlag(rootCmd, "deployer_workers_num", 1, "Number of deployer workers"); err != nil {
return fmt.Errorf("failed to bind deployer_workers_num flag: %w", err)
}
// === Health Checks ===
if err := bindIntFlag(rootCmd, "cluster_health_check_interval_in_hours", 1, "Cluster health check interval (hours)"); err != nil {
return fmt.Errorf("failed to bind cluster_health_check_interval_in_hours flag: %w", err)
}
if err := bindIntFlag(rootCmd, "node_health_check.reserved_node_health_check_interval_in_hours", 1, "Reserved node health check interval (hours)"); err != nil {
return fmt.Errorf("failed to bind node_health_check.reserved_node_health_check_interval_in_hours flag: %w", err)
}
if err := bindIntFlag(rootCmd, "node_health_check.reserved_node_health_check_timeout_in_minutes", 1, "Reserved node health check timeout (minutes)"); err != nil {
return fmt.Errorf("failed to bind node_health_check.reserved_node_health_check_timeout_in_minutes flag: %w", err)
}
if err := bindIntFlag(rootCmd, "node_health_check.reserved_node_health_check_workers_num", 10, "Reserved node health check workers number"); err != nil {
return fmt.Errorf("failed to bind node_health_check.reserved_node_health_check_workers_num flag: %w", err)
}
// === Invoice ===
if err := bindStringFlag(rootCmd, "invoice.name", "", "Invoice company name"); err != nil {
return fmt.Errorf("failed to bind invoice.name flag: %w", err)
}
if err := bindStringFlag(rootCmd, "invoice.address", "", "Invoice address"); err != nil {
return fmt.Errorf("failed to bind invoice.address flag: %w", err)
}
if err := bindStringFlag(rootCmd, "invoice.governorate", "", "Invoice governorate"); err != nil {
return fmt.Errorf("failed to bind invoice.governorate flag: %w", err)
}
// === Debug ===
if err := bindBoolFlag(rootCmd, "debug", false, "Enable debug logging"); err != nil {
return fmt.Errorf("failed to bind debug flag: %w", err)
}
// === Development Mode ===
if err := bindBoolFlag(rootCmd, "dev_mode", false, "Enable development mode"); err != nil {
return fmt.Errorf("failed to bind dev_mode flag: %w", err)
}
// === Monitor Balance Interval In Hours ===
if err := bindIntFlag(rootCmd, "monitor_balance_interval_in_minutes", 1, "Number of minutes to monitor balance"); err != nil {
return fmt.Errorf("failed to bind monitor_balance_interval_in_minutes flag: %w", err)
}
if err := bindIntFlag(rootCmd, "notify_admins_for_pending_records_in_hours", 1, "Number of hours to notify admins about pending records"); err != nil {
return fmt.Errorf("failed to bind notify_admins_for_pending_records_in_hours flag: %w", err)
}
// === Users Balance Check Interval In Hours ===
if err := bindIntFlag(rootCmd, "users_balance_check_interval_in_hours", 6, "Number of hours to check users balance"); err != nil {
return fmt.Errorf("failed to bind users_balance_check_interval_in_hours flag: %w", err)
}
if err := bindIntFlag(rootCmd, "check_user_debt_interval_in_hours", 48, "Number of upcoming hours to check user debt"); err != nil {
return fmt.Errorf("failed to bind check_user_debt_interval_in_hours flag: %w", err)
}
// === Logger Config ===
if err := bindStringFlag(rootCmd, "logger.log_dir", "./logs", "Logger directory"); err != nil {
return fmt.Errorf("failed to bind logger.log_dir flag: %w", err)
}
if err := bindIntFlag(rootCmd, "logger.max_size", 512, "Logger max size (MB)"); err != nil {
return fmt.Errorf("failed to bind logger.max_size flag: %w", err)
}
if err := bindIntFlag(rootCmd, "logger.max_backups", 12, "Logger max backups"); err != nil {
return fmt.Errorf("failed to bind logger.max_backups flag: %w", err)
}
if err := bindIntFlag(rootCmd, "logger.max_age_days", 30, "Logger max age (days)"); err != nil {
return fmt.Errorf("failed to bind logger.max_age_days flag: %w", err)
}
if err := bindBoolFlag(rootCmd, "logger.compress", true, "Logger compress backups"); err != nil {
return fmt.Errorf("failed to bind logger.compress flag: %w", err)
}
// === Loki ===
if err := bindStringFlag(rootCmd, "loki.url", "http://loki:3100/loki/api/v1/push", "Loki URL"); err != nil {
return fmt.Errorf("failed to bind loki.url flag: %w", err)
}
if err := bindIntFlag(rootCmd, "loki.flush_interval_second", 2, "Loki flush interval (seconds)"); err != nil {
return fmt.Errorf("failed to bind loki.flush_interval_second flag: %w", err)
}
// === Loki Labels ===
if err := bindStringFlag(rootCmd, "loki.labels.app", "myceliumcloud", "Loki app label"); err != nil {
return fmt.Errorf("failed to bind loki.labels.app flag: %w", err)
}
if err := bindStringFlag(rootCmd, "loki.labels.env", "main", "Loki env label"); err != nil {
return fmt.Errorf("failed to bind loki.labels.env flag: %w", err)
}
if err := bindStringFlag(rootCmd, "loki.labels.host", "localhost", "Loki host label"); err != nil {
return fmt.Errorf("failed to bind loki.labels.host flag: %w", err)
}
// === Telemetry ===
if err := bindStringFlag(rootCmd, "telemetry.otlp_endpoint", "jaeger:4317", "OpenTelemetry gRPC endpoint"); err != nil {
return fmt.Errorf("failed to bind telemetry.otlp_endpoint flag: %w", err)
}
// === Lock Timeout In Hours ===
if err := bindIntFlag(rootCmd, "lock_timeout_in_hours", 1, "Redis lock timeout (hours)"); err != nil {
return fmt.Errorf("failed to bind lock_timeout_in_hours flag: %w", err)
}
return nil
}
// reloadNotificationsCmd represents the reload-notifications command
var reloadNotificationsCmd = &cobra.Command{
Use: "reload-notifications",
Short: "Reload notification configuration",
Long: `Send a SIGHUP signal to reload notification configuration without restarting the server.`,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Reloading notification configuration...")
socketPath := "/tmp/myceliumcloud.sock"
// Connect to socket
conn, err := net.Dial("unix", socketPath)
if err != nil {
return fmt.Errorf("failed to connect to myceliumcloud (is it running?): %w", err)
}
defer conn.Close()
// Send command
_, err = conn.Write([]byte("reload-notifications"))
if err != nil {
return fmt.Errorf("failed to send command: %w", err)
}
// Read response
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
response := string(buffer[:n])
if strings.HasPrefix(response, "ERROR:") {
return fmt.Errorf("server error: %s", strings.TrimPrefix(response, "ERROR: "))
}
fmt.Printf("%s\n", strings.TrimPrefix(response, "OK: "))
return nil
},
}
func init() {
cobra.OnInitialize(initConfig)
viper.SetEnvPrefix("myceliumcloud") // Prefix for environment variables
viper.AutomaticEnv() // Automatically bind environment variables
// Map environment variables to their corresponding keys
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// Add subcommands
rootCmd.AddCommand(reloadNotificationsCmd)
if err := addFlags(); err != nil {
logger.GetLogger().Fatal().Err(err).Msg("Failed to add flags")
}
}
func initConfig() {
configFile := viper.GetString("config")
if configFile != "" {
viper.SetConfigFile(configFile)
} else {
viper.SetConfigName("config")
viper.SetConfigType("json")
}
if err := viper.ReadInConfig(); err != nil {
logger.GetLogger().Warn().Err(err).Msg("No configuration file found, using defaults")
}
}
var rootCmd = &cobra.Command{
Use: "MyceliumCloud",
Short: "Deploy secure, decentralized Kubernetes clusters on TFGrid with Mycelium networking and QSFS storage.",
Long: `Mycelium Cloud is a CLI tool that helps you deploy and manage Kubernetes clusters on the decentralized TFGrid.
It supports:
- GPU and dedicated nodes for high-performance workloads
- Built-in storage using QSFS with backup and restore
- Private networking with Mycelium (no public IPs needed)
- Web gateway (WebGW) access to expose services
- Usage-based billing with USD pricing set by farmers
- Secure access control through Mycelium whitelisting
`,
RunE: func(cmd *cobra.Command, args []string) error {
config, err := cfg.LoadConfig()
if err != nil {
logger.GetLogger().Error().Err(err).Msg("Failed to read configurations")
return fmt.Errorf("failed to read configuration: %w", err)
}
// === LOGGER SETUP ===
// Initialize shared logger
loggerConfig := logger.LoggerConfig{
LogDir: config.Logger.LogDir,
MaxSize: config.Logger.MaxSize,
MaxBackups: config.Logger.MaxBackups,
MaxAge: config.Logger.MaxAgeDays,
Compress: config.Logger.Compress,
}
if loggerConfig.LogDir == "" {
loggerConfig.LogDir = "./logs"
}
if config.Loki.Labels == nil {
return fmt.Errorf("failed to initialize logger: loki.labels is nil")
}
fmt.Printf("Setting up logging to: %s/app.log\n", loggerConfig.LogDir)
lokiConfig := &logger.LokiConfig{
URL: config.Loki.URL,
FlushInterval: time.Duration(config.Loki.FlushIntervalSecond) * time.Second,
Labels: map[string]string{
"app": config.Loki.Labels.App,
"env": config.Loki.Labels.Env,
"host": config.Loki.Labels.Host,
},
}
if err := logger.InitLogger(loggerConfig, lokiConfig, config.Debug); err != nil {
return fmt.Errorf("failed to initialize logger: %w", err)
}
app, err := app.NewApp(cmd.Context(), config)
if err != nil {
return fmt.Errorf("failed to create new app: %w", err)
}
return gracefulShutdown(app)
},
}
func gracefulShutdown(app *app.App) error {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
logger.GetLogger().Info().Msg("Starting Mycelium Cloud server")
if err := app.Run(); err != nil && err != http.ErrServerClosed {
logger.GetLogger().Error().Err(err).Msg("Failed to start server")
stop()
}
}()
<-ctx.Done()
logger.GetLogger().Info().Msg("Shutting down...")
if err := app.Shutdown(); err != nil {
logger.GetLogger().Error().Err(err).Msg("Server shutdown failed")
return err
}
logger.GetLogger().Info().Msg("Server gracefully stopped.")
return nil
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
logger.GetLogger().Error().Err(err).Msg("Command execution failed")
os.Exit(1)
}
}
func bindStringFlag(cmd *cobra.Command, key, defaultVal, usage string) error {
cmd.PersistentFlags().String(key, defaultVal, usage)
return viper.BindPFlag(key, cmd.PersistentFlags().Lookup(key))
}
func bindIntFlag(cmd *cobra.Command, key string, defaultVal int, usage string) error {
cmd.PersistentFlags().Int(key, defaultVal, usage)
if err := viper.BindPFlag(key, cmd.PersistentFlags().Lookup(key)); err != nil {
return fmt.Errorf("failed to bind flag %s: %w", key, err)
}
if err := viper.BindEnv(key); err != nil {
return fmt.Errorf("failed to bind environment variable for flag %s: %w", key, err)
}
// Ensure the value is set as an integer in viper
viper.Set(key, viper.GetInt(key))
return nil
}
func bindBoolFlag(cmd *cobra.Command, key string, defaultVal bool, usage string) error {
cmd.PersistentFlags().Bool(key, defaultVal, usage)
if err := viper.BindPFlag(key, cmd.PersistentFlags().Lookup(key)); err != nil {
return fmt.Errorf("failed to bind flag %s: %w", key, err)
}
return nil
}