Skip to content

Commit c6a68a5

Browse files
committed
adds systemd command to help generating the service
1 parent 74f847d commit c6a68a5

File tree

3 files changed

+280
-74
lines changed

3 files changed

+280
-74
lines changed

cmd/systemd.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright © 2024 Juliano Martinez <[email protected]>
2+
Copyright © 2024 Juliano Martinez
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -17,31 +17,63 @@ package cmd
1717

1818
import (
1919
"fmt"
20+
"os"
21+
2022
"github.com/ncode/tagit/pkg/systemd"
2123
"github.com/spf13/cobra"
2224
)
2325

2426
// systemdCmd represents the systemd command
2527
var systemdCmd = &cobra.Command{
2628
Use: "systemd",
27-
Short: "systemd generate a systemd service, that you can use for the tagit service",
29+
Short: "Generate a systemd service file for TagIt",
30+
Long: `The systemd command generates a systemd service file for TagIt.
31+
This allows you to easily set up TagIt as a system service that starts
32+
automatically on boot and can be managed using systemctl.
33+
34+
Example usage:
35+
tagit systemd --service-id=my-service --script=/path/to/script.sh --tag-prefix=tagit --interval=5s --user=tagit --group=tagit
36+
`,
2837
Run: func(cmd *cobra.Command, args []string) {
29-
fields := &systemd.Fields{
30-
User: cmd.PersistentFlags().Lookup("user").Value.String(),
31-
Group: cmd.PersistentFlags().Lookup("group").Value.String(),
32-
ConsulAddr: cmd.InheritedFlags().Lookup("consul-addr").Value.String(),
33-
ServiceID: cmd.InheritedFlags().Lookup("service-id").Value.String(),
34-
Script: cmd.InheritedFlags().Lookup("script").Value.String(),
35-
TagPrefix: cmd.InheritedFlags().Lookup("tag-prefix").Value.String(),
36-
Interval: cmd.InheritedFlags().Lookup("interval").Value.String(),
37-
Token: cmd.InheritedFlags().Lookup("token").Value.String(),
38+
flags := make(map[string]string)
39+
for _, flag := range append(systemd.GetRequiredFlags(), systemd.GetOptionalFlags()...) {
40+
flags[flag], _ = cmd.Flags().GetString(flag)
3841
}
39-
fmt.Println(systemd.RenderTemplate(fields))
42+
43+
fields, err := systemd.NewFieldsFromFlags(flags)
44+
if err != nil {
45+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
46+
os.Exit(1)
47+
}
48+
49+
serviceFile, err := systemd.RenderTemplate(fields)
50+
if err != nil {
51+
fmt.Fprintf(os.Stderr, "Error generating systemd service file: %v\n", err)
52+
os.Exit(1)
53+
}
54+
55+
fmt.Println(serviceFile)
4056
},
4157
}
4258

4359
func init() {
4460
rootCmd.AddCommand(systemdCmd)
45-
systemdCmd.PersistentFlags().StringP("user", "u", "nobody", "user to run the service")
46-
systemdCmd.PersistentFlags().StringP("group", "g", "nobody", "group to run the service")
61+
62+
// Define flags for all required and optional fields
63+
systemdCmd.Flags().String("service-id", "", "ID of the service (required)")
64+
systemdCmd.Flags().String("script", "", "Path to the script to execute (required)")
65+
systemdCmd.Flags().String("tag-prefix", "", "Prefix for tags (required)")
66+
systemdCmd.Flags().String("interval", "", "Interval for script execution (required)")
67+
systemdCmd.Flags().String("token", "", "Consul token (optional)")
68+
systemdCmd.Flags().String("consul-addr", "", "Consul address (optional)")
69+
systemdCmd.Flags().String("user", "", "User to run the service as (required)")
70+
systemdCmd.Flags().String("group", "", "Group to run the service as (required)")
71+
72+
// Mark required flags
73+
systemdCmd.MarkFlagRequired("service-id")
74+
systemdCmd.MarkFlagRequired("script")
75+
systemdCmd.MarkFlagRequired("tag-prefix")
76+
systemdCmd.MarkFlagRequired("interval")
77+
systemdCmd.MarkFlagRequired("user")
78+
systemdCmd.MarkFlagRequired("group")
4779
}

pkg/systemd/systemd.go

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,14 @@ package systemd
22

33
import (
44
"bytes"
5+
"fmt"
6+
"strings"
57
"text/template"
68
)
79

8-
// Fields is the struct that holds the fields for the systemd service.
9-
type Fields struct {
10-
ServiceID string
11-
Script string
12-
TagPrefix string
13-
Interval string
14-
Token string
15-
ConsulAddr string
16-
User string
17-
Group string
18-
}
19-
20-
// serviceTemplate is the template for the systemd service.
21-
var serviceTemplate = `
10+
const (
11+
templateName = "serviceTemplate"
12+
templateContents = `
2213
[Unit]
2314
Description=Tagit {{ .ServiceID }}
2415
After=network.target
@@ -27,7 +18,7 @@ Wants=network-online.target
2718
2819
[Service]
2920
Type=simple
30-
ExecStart=/usr/bin/tagit run -s {{ .ServiceID }} -x {{ .Script }} -p {{ .TagPrefix }} -i {{ .Interval }}{{- if .Token }} -t {{ .Token }}{{- end }}{{ if .ConsulAddr }} -c {{ .ConsulAddr }}{{- end }}
21+
ExecStart=/usr/bin/tagit run -s {{ .ServiceID }} -x {{ .Script }} -p {{ .TagPrefix }} -i {{ .Interval }}{{ if .Token }} -t {{ .Token }}{{ end }}{{ if .ConsulAddr }} -c {{ .ConsulAddr }}{{ end }}
3122
Environment=HOME=/var/run/taggit/{{ .ServiceID }}
3223
Restart=always
3324
User={{ .User }}
@@ -36,19 +27,100 @@ Group={{ .Group }}
3627
[Install]
3728
WantedBy=multi-user.target
3829
`
30+
)
31+
32+
// Fields is the struct that holds the fields for the systemd service.
33+
type Fields struct {
34+
ServiceID string
35+
Script string
36+
TagPrefix string
37+
Interval string
38+
Token string
39+
ConsulAddr string
40+
User string
41+
Group string
42+
}
43+
44+
var parsedTemplate *template.Template
45+
46+
func init() {
47+
var err error
48+
parsedTemplate, err = template.New(templateName).Parse(templateContents)
49+
if err != nil {
50+
panic(fmt.Sprintf("Failed to parse template: %v", err))
51+
}
52+
}
3953

4054
// RenderTemplate renders the template for the systemd service.
4155
func RenderTemplate(fields *Fields) (string, error) {
42-
tmpl, err := template.New("serviceTemplate").Parse(serviceTemplate)
43-
if err != nil {
44-
return "", err
56+
if err := validateFields(fields); err != nil {
57+
return "", fmt.Errorf("field validation failed: %w", err)
4558
}
4659

4760
var tmplBuffer bytes.Buffer
48-
err = tmpl.Execute(&tmplBuffer, fields)
61+
err := parsedTemplate.Execute(&tmplBuffer, fields)
4962
if err != nil {
50-
return "", err
63+
return "", fmt.Errorf("failed to execute template: %w", err)
5164
}
5265

5366
return tmplBuffer.String(), nil
5467
}
68+
69+
func validateFields(fields *Fields) error {
70+
var missingFields []string
71+
72+
if fields.ServiceID == "" {
73+
missingFields = append(missingFields, "ServiceID")
74+
}
75+
if fields.Script == "" {
76+
missingFields = append(missingFields, "Script")
77+
}
78+
if fields.TagPrefix == "" {
79+
missingFields = append(missingFields, "TagPrefix")
80+
}
81+
if fields.Interval == "" {
82+
missingFields = append(missingFields, "Interval")
83+
}
84+
if fields.User == "" {
85+
missingFields = append(missingFields, "User")
86+
}
87+
if fields.Group == "" {
88+
missingFields = append(missingFields, "Group")
89+
}
90+
91+
if len(missingFields) > 0 {
92+
return fmt.Errorf("missing required fields: %s", strings.Join(missingFields, ", "))
93+
}
94+
95+
return nil
96+
}
97+
98+
// NewFieldsFromFlags creates a new Fields struct from command line flags.
99+
func NewFieldsFromFlags(flags map[string]string) (*Fields, error) {
100+
fields := &Fields{
101+
ServiceID: flags["service-id"],
102+
Script: flags["script"],
103+
TagPrefix: flags["tag-prefix"],
104+
Interval: flags["interval"],
105+
Token: flags["token"],
106+
ConsulAddr: flags["consul-addr"],
107+
User: flags["user"],
108+
Group: flags["group"],
109+
}
110+
111+
if err := validateFields(fields); err != nil {
112+
return nil, err
113+
}
114+
115+
return fields, nil
116+
}
117+
118+
// GetRequiredFlags returns a list of required flag names.
119+
func GetRequiredFlags() []string {
120+
return []string{"service-id", "script", "tag-prefix", "interval", "user", "group"}
121+
}
122+
123+
// GetOptionalFlags returns a list of optional flag names.
124+
func GetOptionalFlags() []string {
125+
return []string{"token", "consul-addr"}
126+
}

0 commit comments

Comments
 (0)