Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions cmd/daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package main

import (
"context"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"runtime"
"syscall"

"github.com/ThreeDotsLabs/watermill"
Expand All @@ -26,6 +28,16 @@ var (
)

func main() {
// Handle version flag first, before any service initialization
showVersion := flag.Bool("v", false, "Show version information")
showVersionLong := flag.Bool("version", false, "Show version information")
flag.Parse()

if *showVersion || *showVersionLong {
printVersionInfo()
return
}

l := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
Expand Down Expand Up @@ -154,3 +166,13 @@ func main() {
pubsub.Close()
processor.Stop()
}

func printVersionInfo() {
fmt.Printf("shelltime-daemon %s\n", version)
fmt.Printf(" Commit: %s\n", commit)
fmt.Printf(" Build Date: %s\n", date)
fmt.Printf(" Go Version: %s\n", runtime.Version())
fmt.Printf(" OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
fmt.Println()
fmt.Println("Use 'shelltime daemon status' to check the running daemon status.")
}
39 changes: 34 additions & 5 deletions commands/daemon.status.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package commands

import (
"encoding/json"
"fmt"
"net"
"os"
"time"

"github.com/gookit/color"
"github.com/malamtime/cli/daemon"
"github.com/malamtime/cli/model"
"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -37,8 +39,9 @@ func commandDaemonStatus(c *cli.Context) error {
printError(fmt.Sprintf("Socket file does not exist at %s", socketPath))
}

// Check 2: Socket connectivity
connected, latency, connErr := checkSocketConnection(socketPath, 2*time.Second)
// Check 2: Socket connectivity and get status
statusResp, latency, connErr := requestDaemonStatus(socketPath, 2*time.Second)
connected := statusResp != nil
if connected {
printSuccess(fmt.Sprintf("Daemon is responding (latency: %v)", latency.Round(time.Microsecond)))
} else {
Expand All @@ -59,6 +62,15 @@ func commandDaemonStatus(c *cli.Context) error {
}
}

// Daemon info section (if connected)
if statusResp != nil {
printSectionHeader("Daemon Info")
fmt.Printf(" Version: %s\n", statusResp.Version)
fmt.Printf(" Uptime: %s (since %s)\n", statusResp.Uptime, statusResp.StartedAt.Format("2006-01-02 15:04:05"))
fmt.Printf(" Go Version: %s\n", statusResp.GoVersion)
fmt.Printf(" Platform: %s\n", statusResp.Platform)
}

// Configuration section
printSectionHeader("Configuration")
fmt.Printf(" Socket Path: %s\n", socketPath)
Expand Down Expand Up @@ -97,13 +109,30 @@ func checkSocketFileExists(socketPath string) bool {
return err == nil
}

func checkSocketConnection(socketPath string, timeout time.Duration) (bool, time.Duration, error) {
func requestDaemonStatus(socketPath string, timeout time.Duration) (*daemon.StatusResponse, time.Duration, error) {
start := time.Now()
conn, err := net.DialTimeout("unix", socketPath, timeout)
if err != nil {
return false, 0, err
return nil, 0, err
}
defer conn.Close()

// Send status request
msg := daemon.SocketMessage{
Type: daemon.SocketMessageTypeStatus,
}
encoder := json.NewEncoder(conn)
if err := encoder.Encode(msg); err != nil {
return nil, 0, err
}

// Read response
var response daemon.StatusResponse
decoder := json.NewDecoder(conn)
if err := decoder.Decode(&response); err != nil {
return nil, 0, err
}

latency := time.Since(start)
return true, latency, nil
return &response, latency, nil
}
16 changes: 15 additions & 1 deletion daemon/base.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package daemon

import "github.com/malamtime/cli/model"
import (
"time"

"github.com/malamtime/cli/model"
)

var stConfig model.ConfigService
var version string
var startedAt time.Time

const (
PubSubTopic = "socket"
Expand All @@ -12,4 +17,13 @@ const (
func Init(cs model.ConfigService, vs string) {
stConfig = cs
version = vs
startedAt = time.Now()
}

func GetStartedAt() time.Time {
return startedAt
}

func GetVersion() string {
return version
}
1 change: 1 addition & 0 deletions daemon/ccotel_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (p *CCOtelProcessor) writeDebugFile(filename string, data interface{}) {
if _, err := f.WriteString(fmt.Sprintf("\n--- %s ---\n%s\n", timestamp, jsonData)); err != nil {
slog.Error("CCOtel: Failed to write debug data", "error", err)
}
slog.Debug("CCOtel: Wrote debug data", "path", filePath)
}

// ProcessMetrics receives OTEL metrics and forwards to backend immediately
Expand Down
53 changes: 49 additions & 4 deletions daemon/socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package daemon

import (
"encoding/json"
"fmt"
"log/slog"
"net"
"os"
"runtime"
"time"

"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
Expand All @@ -16,8 +19,18 @@ type SocketMessageType string
const (
SocketMessageTypeSync SocketMessageType = "sync"
SocketMessageTypeHeartbeat SocketMessageType = "heartbeat"
SocketMessageTypeStatus SocketMessageType = "status"
)

// StatusResponse contains daemon status information
type StatusResponse struct {
Version string `json:"version"`
StartedAt time.Time `json:"startedAt"`
Uptime string `json:"uptime"`
GoVersion string `json:"goVersion"`
Platform string `json:"platform"`
}

type SocketMessage struct {
Type SocketMessageType `json:"type"`
// if parse from buffer, it will be the map[any]any
Expand Down Expand Up @@ -99,10 +112,8 @@ func (p *SocketHandler) handleConnection(conn net.Conn) {
}

switch msg.Type {
// case "status":
// p.handleStatus(conn)
// case "track":
// p.handleTrack(conn, msg.Payload)
case SocketMessageTypeStatus:
p.handleStatus(conn)
case SocketMessageTypeSync:
buf, err := json.Marshal(msg)
if err != nil {
Expand Down Expand Up @@ -133,3 +144,37 @@ func (p *SocketHandler) handleConnection(conn net.Conn) {
slog.Error("Unknown message type:", slog.String("messageType", string(msg.Type)))
}
}

func (p *SocketHandler) handleStatus(conn net.Conn) {
uptime := time.Since(startedAt)
response := StatusResponse{
Version: version,
StartedAt: startedAt,
Uptime: formatDuration(uptime),
GoVersion: runtime.Version(),
Platform: runtime.GOOS + "/" + runtime.GOARCH,
}

encoder := json.NewEncoder(conn)
if err := encoder.Encode(response); err != nil {
slog.Error("Error encoding status response", slog.Any("err", err))
}
}

func formatDuration(d time.Duration) string {
days := int(d.Hours() / 24)
hours := int(d.Hours()) % 24
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60

if days > 0 {
return fmt.Sprintf("%dd %dh %dm %ds", days, hours, minutes, seconds)
}
if hours > 0 {
return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds)
}
if minutes > 0 {
return fmt.Sprintf("%dm %ds", minutes, seconds)
}
return fmt.Sprintf("%ds", seconds)
}
4 changes: 4 additions & 0 deletions model/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ func (cs *configService) ReadConfigFile(ctx context.Context, opts ...ReadConfigO
if config.CCOtel != nil && config.CCOtel.GRPCPort == 0 {
config.CCOtel.GRPCPort = 54027 // default OTEL gRPC port
}

if config.CCOtel != nil && config.CCOtel.Debug != nil && *config.CCOtel.Debug {
config.CCOtel.Debug = &truthy
}
if config.SocketPath == "" {
config.SocketPath = DefaultSocketPath
}
Expand Down
Loading