Skip to content

Commit 5a0854c

Browse files
committed
refactor: create service package to manage systemd/launchd
1 parent ff89a20 commit 5a0854c

File tree

5 files changed

+226
-110
lines changed

5 files changed

+226
-110
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ install: check_version
3636
go install -ldflags "$(LDFLAGS)" .
3737

3838
test: check_version
39+
go clean -testcache
3940
go test -v ./...
4041

4142
# Release process

cmd/initia.go

+23-110
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,17 @@
11
package cmd
22

33
import (
4-
"bufio"
54
"fmt"
6-
"io"
7-
"os"
8-
"os/exec"
9-
"os/signal"
10-
"path/filepath"
11-
"runtime"
12-
"syscall"
13-
"time"
145

156
tea "github.com/charmbracelet/bubbletea"
167
"github.com/spf13/cobra"
178

189
"github.com/initia-labs/weave/models"
1910
"github.com/initia-labs/weave/models/initia"
11+
"github.com/initia-labs/weave/service"
2012
"github.com/initia-labs/weave/utils"
2113
)
2214

23-
const (
24-
PreviousLogLines = 100
25-
)
26-
2715
func InitiaCommand() *cobra.Command {
2816
cmd := &cobra.Command{
2917
Use: "initia",
@@ -78,10 +66,15 @@ func initiaStartCommand() *cobra.Command {
7866
Use: "start",
7967
Short: "Start the initiad full node application.",
8068
RunE: func(cmd *cobra.Command, args []string) error {
81-
err := utils.StartService(utils.GetRunL1NodeServiceName())
69+
s, err := service.NewService(service.Initia)
70+
if err != nil {
71+
return err
72+
}
73+
err = s.Start()
8274
if err != nil {
8375
return err
8476
}
77+
fmt.Println("Started Initia full node application. You can see the logs with `initia log`")
8578
return nil
8679
},
8780
}
@@ -94,10 +87,15 @@ func initiaStopCommand() *cobra.Command {
9487
Use: "stop",
9588
Short: "Stop the initiad full node application.",
9689
RunE: func(cmd *cobra.Command, args []string) error {
97-
err := utils.StopService(utils.GetRunL1NodeServiceName())
90+
s, err := service.NewService(service.Initia)
9891
if err != nil {
9992
return err
10093
}
94+
err = s.Stop()
95+
if err != nil {
96+
return err
97+
}
98+
fmt.Println("Stopped Initia full node application.")
10199
return nil
102100
},
103101
}
@@ -106,124 +104,39 @@ func initiaStopCommand() *cobra.Command {
106104
}
107105

108106
func initiaRestartCommand() *cobra.Command {
109-
reStartCmd := &cobra.Command{
107+
restartCmd := &cobra.Command{
110108
Use: "restart",
111109
Short: "Restart the initiad full node application.",
112110
RunE: func(cmd *cobra.Command, args []string) error {
113-
err := utils.StopService(utils.GetRunL1NodeServiceName())
111+
s, err := service.NewService(service.Initia)
114112
if err != nil {
115113
return err
116114
}
117-
118-
err = utils.StartService(utils.GetRunL1NodeServiceName())
115+
err = s.Restart()
119116
if err != nil {
120117
return err
121118
}
119+
120+
fmt.Println("Started Initia full node application. You can see the logs with `initia log`")
122121
return nil
123122
},
124123
}
125124

126-
return reStartCmd
125+
return restartCmd
127126
}
128127

129128
func initiaLogCommand() *cobra.Command {
130129
logCmd := &cobra.Command{
131130
Use: "log",
132131
Short: "Stream the logs of the initiad full node application.",
133132
RunE: func(cmd *cobra.Command, args []string) error {
134-
// Check if the OS is Linux
135-
switch runtime.GOOS {
136-
case "linux":
137-
return streamLogsFromJournalctl()
138-
case "darwin":
139-
// If not Linux, fall back to file-based log streaming
140-
return streamLogsFromFiles()
141-
default:
142-
return fmt.Errorf("unsupported OS: %s", runtime.GOOS)
133+
s, err := service.NewService(service.Initia)
134+
if err != nil {
135+
return err
143136
}
137+
return s.Log()
144138
},
145139
}
146140

147141
return logCmd
148142
}
149-
150-
// streamLogsFromJournalctl uses journalctl to stream logs from initia.service
151-
func streamLogsFromJournalctl() error {
152-
// Execute the journalctl command to follow logs of initia.service
153-
cmd := exec.Command("journalctl", "-f", "-u", "initia.service")
154-
cmd.Stdout = os.Stdout
155-
cmd.Stderr = os.Stderr
156-
157-
// Run the command and return any errors
158-
if err := cmd.Run(); err != nil {
159-
return fmt.Errorf("failed to stream logs using journalctl: %v", err)
160-
}
161-
162-
return nil
163-
}
164-
165-
// streamLogsFromFiles streams logs from file-based logs
166-
func streamLogsFromFiles() error {
167-
userHome, err := os.UserHomeDir()
168-
if err != nil {
169-
return fmt.Errorf("failed to get user home directory: %v", err)
170-
}
171-
172-
logFilePathOut := filepath.Join(userHome, utils.WeaveLogDirectory, "initia.stdout.log")
173-
logFilePathErr := filepath.Join(userHome, utils.WeaveLogDirectory, "initia.stderr.log")
174-
175-
sigChan := make(chan os.Signal, 1)
176-
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
177-
178-
go tailLogFile(logFilePathOut, os.Stdout)
179-
go tailLogFile(logFilePathErr, os.Stderr)
180-
181-
<-sigChan
182-
183-
fmt.Println("Stopping log streaming...")
184-
return nil
185-
}
186-
187-
func tailLogFile(filePath string, output io.Writer) {
188-
file, err := os.Open(filePath)
189-
if err != nil {
190-
fmt.Printf("error opening log file %s: %v\n", filePath, err)
191-
return
192-
}
193-
defer file.Close()
194-
195-
var lines []string
196-
scanner := bufio.NewScanner(file)
197-
198-
for scanner.Scan() {
199-
lines = append(lines, scanner.Text())
200-
if len(lines) > PreviousLogLines {
201-
lines = lines[1:]
202-
}
203-
}
204-
205-
for _, line := range lines {
206-
fmt.Fprintln(output, line)
207-
}
208-
209-
_, err = file.Seek(0, io.SeekEnd)
210-
if err != nil {
211-
fmt.Printf("error seeking to end of log file %s: %v\n", filePath, err)
212-
return
213-
}
214-
215-
for {
216-
var line = make([]byte, 4096)
217-
n, err := file.Read(line)
218-
if err != nil && err != io.EOF {
219-
fmt.Printf("error reading log file %s: %v\n", filePath, err)
220-
return
221-
}
222-
223-
if n > 0 {
224-
output.Write(line[:n])
225-
} else {
226-
time.Sleep(1 * time.Second)
227-
}
228-
}
229-
}

service/launchd.go

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package service
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"os"
8+
"os/exec"
9+
"os/signal"
10+
"path/filepath"
11+
"syscall"
12+
"time"
13+
14+
"github.com/initia-labs/weave/utils"
15+
)
16+
17+
const (
18+
PreviousLogLines = 100
19+
)
20+
21+
type Launchd struct {
22+
commandName CommandName
23+
}
24+
25+
func NewLaunchd(commandName CommandName) *Launchd {
26+
return &Launchd{commandName: commandName}
27+
}
28+
29+
func (j *Launchd) GetServiceName() string {
30+
return "com." + string(j.commandName) + ".daemon"
31+
}
32+
33+
func (j *Launchd) Start() error {
34+
cmd := exec.Command("launchctl", "start", j.GetServiceName())
35+
return cmd.Run()
36+
}
37+
38+
func (j *Launchd) Stop() error {
39+
cmd := exec.Command("launchctl", "stop", j.GetServiceName())
40+
return cmd.Run()
41+
}
42+
43+
func (j *Launchd) Restart() error {
44+
err := j.Stop()
45+
if err != nil {
46+
return fmt.Errorf("failed to stop service: %v", err)
47+
}
48+
err = j.Start()
49+
if err != nil {
50+
return fmt.Errorf("failed to start service: %v", err)
51+
}
52+
return nil
53+
}
54+
55+
func (j *Launchd) Log() error {
56+
fmt.Printf("Streaming logs from launchd %s\n", j.GetServiceName())
57+
return j.streamLogsFromFiles()
58+
}
59+
60+
// streamLogsFromFiles streams logs from file-based logs
61+
func (j *Launchd) streamLogsFromFiles() error {
62+
userHome, err := os.UserHomeDir()
63+
if err != nil {
64+
return fmt.Errorf("failed to get user home directory: %v", err)
65+
}
66+
67+
logFilePathOut := filepath.Join(userHome, utils.WeaveLogDirectory, "initia.stdout.log")
68+
logFilePathErr := filepath.Join(userHome, utils.WeaveLogDirectory, "initia.stderr.log")
69+
70+
sigChan := make(chan os.Signal, 1)
71+
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
72+
73+
go j.tailLogFile(logFilePathOut, os.Stdout)
74+
go j.tailLogFile(logFilePathErr, os.Stderr)
75+
76+
<-sigChan
77+
78+
fmt.Println("Stopping log streaming...")
79+
return nil
80+
}
81+
82+
func (j *Launchd) tailLogFile(filePath string, output io.Writer) {
83+
file, err := os.Open(filePath)
84+
if err != nil {
85+
fmt.Printf("error opening log file %s: %v\n", filePath, err)
86+
return
87+
}
88+
defer file.Close()
89+
90+
var lines []string
91+
scanner := bufio.NewScanner(file)
92+
93+
for scanner.Scan() {
94+
lines = append(lines, scanner.Text())
95+
if len(lines) > PreviousLogLines {
96+
lines = lines[1:]
97+
}
98+
}
99+
100+
for _, line := range lines {
101+
fmt.Fprintln(output, line)
102+
}
103+
104+
_, err = file.Seek(0, io.SeekEnd)
105+
if err != nil {
106+
fmt.Printf("error seeking to end of log file %s: %v\n", filePath, err)
107+
return
108+
}
109+
110+
for {
111+
var line = make([]byte, 4096)
112+
n, err := file.Read(line)
113+
if err != nil && err != io.EOF {
114+
fmt.Printf("error reading log file %s: %v\n", filePath, err)
115+
return
116+
}
117+
118+
if n > 0 {
119+
output.Write(line[:n])
120+
} else {
121+
time.Sleep(1 * time.Second)
122+
}
123+
}
124+
}

service/service.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package service
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
)
7+
8+
type CommandName string
9+
10+
const (
11+
Initia CommandName = "initia"
12+
Minitia CommandName = "minitia"
13+
14+
DarwinServiceName = "com.initia.daemon"
15+
)
16+
17+
type Service interface {
18+
Log() error
19+
Start() error
20+
Stop() error
21+
Restart() error
22+
}
23+
24+
func NewService(commandName CommandName) (Service, error) {
25+
switch runtime.GOOS {
26+
case "linux":
27+
return NewSystemd(Initia), nil
28+
case "darwin":
29+
return NewLaunchd(Initia), nil
30+
default:
31+
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
32+
}
33+
}

0 commit comments

Comments
 (0)