package s6netdev import ( "errors" "fmt" "io/fs" "os" "path/filepath" "strings" ) type S6SvcTree struct { s map[S6SvcName]*S6Svc rcdir, envdir string root string } type kv_file struct { name, value string perm int } type S6Svc struct { Name S6SvcName Type S6SvcType Children []*S6Svc // Either dependencies or contents depending on type ProducerFor, ConsumerFor *S6Svc NotifFd int FlagEssential bool Up, Down, Run string env map[string]string } type S6SvcName string func (s S6SvcName) Sanitize() S6SvcName { // Only sanitize slashes for now return S6SvcName(strings.ReplaceAll(string(s), "/", "_")) } type S6SvcType *string var S6SvcTypes = struct { Oneshot, Longrun, Bundle string }{ Oneshot: "oneshot", Longrun: "longrun", Bundle: "bundle", } func S6NewTree() *S6SvcTree { t := new(S6SvcTree) t.s = make(map[S6SvcName]*S6Svc) t.root = "/etc/s6" t.rcdir = "rc" t.envdir = "env" return t } func (t *S6SvcTree) S6New(name S6SvcName, svc_type S6SvcType) *S6Svc { if name == "" || svc_type == nil { return nil } if e, ok := t.s[name]; ok && e.Type == svc_type { return e } s := new(S6Svc) s.Name = name s.Type = svc_type t.s[name] = s return s } func S6Pipe(src, dst *S6Svc) { src.ProducerFor = dst dst.ConsumerFor = src } func (s *S6Svc) S6Children(children ...*S6Svc) { s.Children = append(s.Children, children...) } func (s *S6Svc) S6AddEnv(key, value string) { s.env[key] = value } func (s *S6Svc) S6DelEnv(key string) { delete(s.env, key) } func (t *S6SvcTree) S6AddSvc(s *S6Svc) { t.s[s.Name] = s } func (t *S6SvcTree) S6GetSvc(name S6SvcName) *S6Svc { return t.s[name] } func (t *S6SvcTree) S6Services() (r []*S6Svc) { for _, v := range t.s { r = append(r, v) } return } func (t *S6SvcTree) S6CommitService(s *S6Svc) (err error) { if err = os.Mkdir(t.rcdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) { return } sdir := filepath.Join(t.rcdir, string(s.Name)) if err = os.Mkdir(sdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) { return } var kvfiles []kv_file kvfiles = append(kvfiles, kv_file{"type", *s.Type, 0666}) if s.FlagEssential { kvfiles = append(kvfiles, kv_file{"flag-essential", "", 0666}) } if s.ProducerFor != nil { kvfiles = append(kvfiles, kv_file{"producer-for", string(s.ProducerFor.Name), 0666}) } if s.ConsumerFor != nil { kvfiles = append(kvfiles, kv_file{"consumer-for", string(s.ConsumerFor.Name), 0666}) } if s.NotifFd != 0 { kvfiles = append(kvfiles, kv_file{"notification-fd", fmt.Sprintf("%d", s.NotifFd), 0666}) } // Scripts switch s.Type { case &S6SvcTypes.Longrun: { if s.Run != "" { kvfiles = append(kvfiles, kv_file{"run", s.Run, 0777}) } } case &S6SvcTypes.Oneshot: { if s.Up != "" { kvfiles = append(kvfiles, kv_file{"up", s.Up, 0777}) } if s.Down != "" { kvfiles = append(kvfiles, kv_file{"down", s.Down, 0777}) } } } for _, v := range kvfiles { var f *os.File if f, err = os.OpenFile(filepath.Join(sdir, v.name), os.O_CREATE|os.O_RDWR, fs.FileMode(v.perm)); err != nil { return } _, err = fmt.Fprintln(f, v.value) f.Close() if err != nil { return } } // Children var dirname string switch s.Type { case &S6SvcTypes.Bundle: dirname = "contents.d" default: dirname = "dependencies.d" } cdir := filepath.Join(sdir, dirname) if err = os.Mkdir(cdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) { return } for _, v := range s.Children { var f *os.File if f, err = os.Create(filepath.Join(cdir, string(v.Name))); err != nil && !errors.Is(err, os.ErrExist) { return } f.Close() } if len(s.env) != 0 { if err = os.Mkdir(t.envdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) { return } sdir := filepath.Join(t.envdir, string(s.Name)) if err = os.Mkdir(sdir, fs.ModeDir|0777); err != nil && !errors.Is(err, os.ErrExist) { return } for k, v := range s.env { var f *os.File if f, err = os.Create(filepath.Join(sdir, string(k))); err != nil && !errors.Is(err, os.ErrExist) { return } _, err = fmt.Fprintln(f, v) f.Close() if err != nil { return } } } return }