package app import ( "bytes" "os" "path/filepath" "syscall" "github.com/pelletier/go-toml/v2" ) // UpdateAccessConfig preserves all existing settings, ownership and permissions. // It never opens the data directory; callers can prepare a candidate while serving. func UpdateAccessConfig(path, listen string, additions []string, allowLAN bool) error { data, err := os.ReadFile(path) if err != nil { return err } info, err := os.Stat(path) if err != nil { return err } cfg := DefaultConfig() if err = toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields().Decode(&cfg); err != nil { return err } if listen != "" { cfg.Listen = listen } cfg.AllowLANHTTP = cfg.AllowLANHTTP || allowLAN for _, raw := range additions { base, e := normalizeAccessURL(raw) if e != nil { return e } found := false for _, existing := range cfg.accessOrigins() { if base == existing { found = true break } } if !found { cfg.AccessURLs = append(cfg.AccessURLs, base) } } if err = cfg.Validate(); err != nil { return err } encoded, err := toml.Marshal(cfg) if err != nil { return err } out, err := os.CreateTemp(filepath.Dir(path), ".access-config-*.toml") if err != nil { return err } defer os.Remove(out.Name()) defer out.Close() if stat, ok := info.Sys().(*syscall.Stat_t); ok { if err = out.Chown(int(stat.Uid), int(stat.Gid)); err != nil { return err } } if err = out.Chmod(info.Mode().Perm()); err != nil { return err } if _, err = out.Write(encoded); err != nil { return err } if err = out.Sync(); err != nil { return err } if err = out.Close(); err != nil { return err } return os.Rename(out.Name(), path) }