Skip to content

Commit fc51c52

Browse files
committed
systemd fixes and tests
1 parent 3b1d31a commit fc51c52

File tree

4 files changed

+257
-10
lines changed

4 files changed

+257
-10
lines changed

.golangci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@ linters:
4545
- whitespace
4646
issues:
4747
exclude-rules:
48-
- path: test
48+
- path: _test\.go|test
4949
linters:
5050
- funlen
5151
- lll
5252
- dupl
5353
- gosec
5454
- scopelint
5555
- govet
56+
- gocognit
5657
- path: internal/app/di
5758
linters:
5859
- govet

internal/processmanager/systemd.go

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
//go:build linux
2-
// +build linux
3-
41
package processmanager
52

63
import (
74
"context"
85
"fmt"
96
"io"
107
"os"
8+
"os/exec"
119
"os/user"
1210
"path/filepath"
1311
"strings"
@@ -16,6 +14,7 @@ import (
1614
"github.com/gameap/daemon/internal/app/contracts"
1715
"github.com/gameap/daemon/internal/app/domain"
1816
"github.com/gameap/daemon/pkg/logger"
17+
"github.com/gameap/daemon/pkg/shellquote"
1918
"github.com/pkg/errors"
2019
)
2120

@@ -333,13 +332,12 @@ func (pm *SystemD) buildServiceConfig(server *domain.Server) (string, error) {
333332

334333
builder.WriteString("Type=simple\n")
335334

335+
cmd, err := pm.makeStartCommand(server)
336+
if err != nil {
337+
return "", errors.WithMessage(err, "failed to make command")
338+
}
336339
builder.WriteString("ExecStart=")
337-
builder.WriteString(
338-
filepath.Join(
339-
server.WorkDir(pm.cfg),
340-
domain.MakeFullCommand(pm.cfg, server, pm.cfg.Scripts.Start, server.StartCommand()),
341-
),
342-
)
340+
builder.WriteString(cmd)
343341
builder.WriteString("\n")
344342

345343
builder.WriteString("Sockets=")
@@ -385,6 +383,41 @@ func (pm *SystemD) buildServiceConfig(server *domain.Server) (string, error) {
385383
return builder.String(), nil
386384
}
387385

386+
func (pm *SystemD) makeStartCommand(server *domain.Server) (string, error) {
387+
startCMD := server.StartCommand()
388+
389+
parts, err := shellquote.Split(startCMD)
390+
if err != nil {
391+
return "", errors.WithMessage(err, "failed to split command")
392+
}
393+
394+
cmd := parts[0]
395+
args := parts[1:]
396+
397+
var foundPath string
398+
399+
if !filepath.IsAbs(cmd) {
400+
foundPath, err = exec.LookPath(filepath.Join(server.WorkDir(pm.cfg), cmd))
401+
if err != nil {
402+
foundPath, err = exec.LookPath(cmd)
403+
if err != nil {
404+
return "", errors.WithMessagef(err, "failed to find command '%s'", cmd)
405+
}
406+
}
407+
}
408+
409+
if filepath.IsAbs(cmd) {
410+
foundPath, err = exec.LookPath(cmd)
411+
if err != nil {
412+
return "", errors.WithMessagef(err, "failed to find command '%s'", cmd)
413+
}
414+
}
415+
416+
startCommand := shellquote.Join(append([]string{foundPath}, args...)...)
417+
418+
return domain.MakeFullCommand(pm.cfg, server, pm.cfg.Scripts.Start, startCommand), nil
419+
}
420+
388421
func (pm *SystemD) makeSocket(ctx context.Context, server *domain.Server) error {
389422
f, err := os.OpenFile(pm.socketFile(server), os.O_CREATE|os.O_WRONLY, 0644)
390423
if err != nil {
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package processmanager
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
"time"
8+
9+
"github.com/gameap/daemon/internal/app/config"
10+
"github.com/gameap/daemon/internal/app/domain"
11+
"github.com/pkg/errors"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func Test_makeCommand(t *testing.T) {
17+
tempDir, err := os.MkdirTemp("", "")
18+
if err != nil {
19+
t.Fatal(errors.WithMessage(err, "failed to create temp dir"))
20+
}
21+
22+
tests := []struct {
23+
name string
24+
server func() *domain.Server
25+
expectedCommand string
26+
expectedError string
27+
}{
28+
{
29+
name: "success with local file",
30+
server: func() *domain.Server {
31+
f, err := os.OpenFile(filepath.Join(tempDir, "start.sh"), os.O_CREATE, 0755)
32+
if err != nil {
33+
t.Fatal(errors.WithMessage(err, "failed to create start.sh"))
34+
}
35+
err = f.Close()
36+
if err != nil {
37+
t.Fatal(errors.WithMessage(err, "failed to close start.sh"))
38+
}
39+
40+
return makeServerWithStartCommandAndDir("start.sh start command", tempDir)
41+
},
42+
expectedCommand: filepath.Join(tempDir, "start.sh start command"),
43+
},
44+
{
45+
name: "success with local file and dot",
46+
server: func() *domain.Server {
47+
f, err := os.OpenFile(filepath.Join(tempDir, "start.sh"), os.O_CREATE, 0755)
48+
if err != nil {
49+
t.Fatal(errors.WithMessage(err, "failed to create start.sh"))
50+
}
51+
err = f.Close()
52+
if err != nil {
53+
t.Fatal(errors.WithMessage(err, "failed to close start.sh"))
54+
}
55+
56+
return makeServerWithStartCommandAndDir("./start.sh start command", tempDir)
57+
},
58+
expectedCommand: filepath.Join(tempDir, "start.sh start command"),
59+
},
60+
{
61+
name: "success with local file and quotes",
62+
server: func() *domain.Server {
63+
f, err := os.OpenFile(filepath.Join(tempDir, "start.sh"), os.O_CREATE, 0755)
64+
if err != nil {
65+
t.Fatal(errors.WithMessage(err, "failed to create start.sh"))
66+
}
67+
err = f.Close()
68+
if err != nil {
69+
t.Fatal(errors.WithMessage(err, "failed to close start.sh"))
70+
}
71+
72+
return makeServerWithStartCommandAndDir("./start.sh 'some quotes' \"some quotes\" args", tempDir)
73+
},
74+
expectedCommand: filepath.Join(tempDir, "./start.sh 'some quotes' 'some quotes' args"),
75+
},
76+
{
77+
name: "success with global file",
78+
server: func() *domain.Server {
79+
return makeServerWithStartCommandAndDir("env --help", tempDir)
80+
},
81+
expectedCommand: "/usr/bin/env --help",
82+
},
83+
{
84+
name: "success with abs path",
85+
server: func() *domain.Server {
86+
return makeServerWithStartCommandAndDir("/usr/bin/env --help", tempDir)
87+
},
88+
expectedCommand: "/usr/bin/env --help",
89+
},
90+
{
91+
name: "success with both existing file",
92+
server: func() *domain.Server {
93+
f, err := os.OpenFile(filepath.Join(tempDir, "env"), os.O_CREATE, 0755)
94+
if err != nil {
95+
t.Fatal(errors.WithMessage(err, "failed to create env file"))
96+
}
97+
err = f.Close()
98+
if err != nil {
99+
t.Fatal(errors.WithMessage(err, "failed to close env file"))
100+
}
101+
102+
return makeServerWithStartCommandAndDir("env --help", tempDir)
103+
},
104+
expectedCommand: filepath.Join(tempDir, "env --help"),
105+
},
106+
{
107+
name: "success with both existing file and dot",
108+
server: func() *domain.Server {
109+
f, err := os.OpenFile(filepath.Join(tempDir, "env"), os.O_CREATE, 0755)
110+
if err != nil {
111+
t.Fatal(errors.WithMessage(err, "failed to create env file"))
112+
}
113+
err = f.Close()
114+
if err != nil {
115+
t.Fatal(errors.WithMessage(err, "failed to close env file"))
116+
}
117+
118+
return makeServerWithStartCommandAndDir("./env --help", tempDir)
119+
},
120+
expectedCommand: filepath.Join(tempDir, "env --help"),
121+
},
122+
{
123+
name: "error invalid command",
124+
server: func() *domain.Server {
125+
return makeServerWithStartCommandAndDir("invalid", tempDir)
126+
},
127+
expectedError: `failed to find command 'invalid'`,
128+
},
129+
{
130+
name: "error invalid global command",
131+
server: func() *domain.Server {
132+
return makeServerWithStartCommandAndDir("/usr/bin/invalid", tempDir)
133+
},
134+
expectedError: `failed to find command '/usr/bin/invalid'`,
135+
},
136+
}
137+
138+
for _, test := range tests {
139+
t.Run(test.name, func(t *testing.T) {
140+
err := filepath.Walk(tempDir, func(path string, _ os.FileInfo, err error) error {
141+
if tempDir == path {
142+
return nil
143+
}
144+
145+
if err != nil {
146+
return err
147+
}
148+
return os.RemoveAll(path)
149+
})
150+
if err != nil {
151+
t.Fatal(errors.WithMessage(err, "failed to remove temp dir content"))
152+
}
153+
154+
server := test.server()
155+
systemd := NewSystemD(&config.Config{
156+
WorkPath: "",
157+
Scripts: config.Scripts{
158+
Start: "{command}",
159+
},
160+
}, nil, nil)
161+
command, err := systemd.makeStartCommand(server)
162+
163+
if test.expectedError != "" {
164+
require.Error(t, err)
165+
assert.Contains(t, err.Error(), test.expectedError)
166+
} else {
167+
require.NoError(t, err)
168+
assert.Equal(t, test.expectedCommand, command)
169+
}
170+
})
171+
}
172+
}
173+
174+
func makeServerWithStartCommandAndDir(startCommand, dir string) *domain.Server {
175+
return domain.NewServer(
176+
1337,
177+
true,
178+
domain.ServerInstalled,
179+
false,
180+
"name",
181+
"759b875e-d910-11eb-aff7-d796d7fcf7ef",
182+
"759b875e",
183+
domain.Game{
184+
StartCode: "cstrike",
185+
},
186+
domain.GameMod{
187+
Name: "public",
188+
},
189+
"1.3.3.7",
190+
1337,
191+
1338,
192+
1339,
193+
"paS$w0rD",
194+
dir,
195+
"gameap-user",
196+
startCommand,
197+
"",
198+
"",
199+
"",
200+
true,
201+
time.Now(),
202+
map[string]string{
203+
"default_map": "de_dust2",
204+
"tickrate": "1000",
205+
},
206+
map[string]string{},
207+
time.Now(),
208+
)
209+
}

pkg/shellquote/unquote.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ func Split(input string) (words []string, err error) {
1818

1919
return shellquote.Split(input)
2020
}
21+
22+
func Join(words ...string) string {
23+
return shellquote.Join(words...)
24+
}

0 commit comments

Comments
 (0)