mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-04-05 08:54:09 -04:00
feat(files): configurable UMASK defaulting to system umask
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
@@ -11,12 +14,14 @@ import (
|
||||
type Paths struct {
|
||||
DataDir *string
|
||||
Config *string
|
||||
Umask fs.FileMode
|
||||
}
|
||||
|
||||
func (p *Paths) setDefaults() {
|
||||
p.DataDir = gosettings.DefaultPointer(p.DataDir, "./data")
|
||||
defaultConfig := filepath.Join(*p.DataDir, "config.json")
|
||||
p.Config = gosettings.DefaultPointer(p.Config, defaultConfig)
|
||||
p.Umask = gosettings.DefaultComparable(p.Umask, getCurrentUmask())
|
||||
}
|
||||
|
||||
func (p Paths) Validate() (err error) {
|
||||
@@ -31,10 +36,30 @@ func (p Paths) toLinesNode() *gotree.Node {
|
||||
node := gotree.New("Paths")
|
||||
node.Appendf("Data directory: %s", *p.DataDir)
|
||||
node.Appendf("Config file: %s", *p.Config)
|
||||
node.Appendf("Umask: %s", p.Umask.String())
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *Paths) read(reader *reader.Reader) {
|
||||
func (p *Paths) read(reader *reader.Reader) (err error) {
|
||||
p.DataDir = reader.Get("DATADIR")
|
||||
p.Config = reader.Get("CONFIG_FILEPATH")
|
||||
|
||||
umaskString := reader.String("UMASK")
|
||||
if umaskString != "" {
|
||||
p.Umask, err = parseUmask(umaskString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse umask: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseUmask(s string) (umask fs.FileMode, err error) {
|
||||
const base, bitSize = 8, 32
|
||||
umaskUint64, err := strconv.ParseUint(s, base, bitSize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fs.FileMode(umaskUint64), nil
|
||||
}
|
||||
|
||||
47
internal/config/paths_test.go
Normal file
47
internal/config/paths_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_parseUmask(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
s string
|
||||
umask fs.FileMode
|
||||
errMessage string
|
||||
}{
|
||||
"invalid": {
|
||||
s: "a",
|
||||
errMessage: `strconv.ParseUint: parsing "a": invalid syntax`,
|
||||
},
|
||||
"704": {
|
||||
s: "704",
|
||||
umask: 0o704,
|
||||
},
|
||||
"0704": {
|
||||
s: "0704",
|
||||
umask: 0o0704,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
umask, err := parseUmask(testCase.s)
|
||||
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.umask, umask)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,10 @@ func (c *Config) Read(reader *reader.Reader,
|
||||
}
|
||||
|
||||
c.Health.Read(reader)
|
||||
c.Paths.read(reader)
|
||||
err = c.Paths.read(reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading paths settings: %w", err)
|
||||
}
|
||||
|
||||
err = c.Backup.read(reader)
|
||||
if err != nil {
|
||||
|
||||
@@ -40,7 +40,8 @@ func Test_Settings_String(t *testing.T) {
|
||||
| └── Server listening address: 127.0.0.1:9999
|
||||
├── Paths
|
||||
| ├── Data directory: ./data
|
||||
| └── Config file: data/config.json
|
||||
| ├── Config file: data/config.json
|
||||
| └── Umask: -----w--w-
|
||||
├── Backup: disabled
|
||||
└── Logger
|
||||
├── Level: INFO
|
||||
|
||||
15
internal/config/umask_unix.go
Normal file
15
internal/config/umask_unix.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build !windows
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getCurrentUmask() (mask fs.FileMode) {
|
||||
const tempMask = 0o022
|
||||
oldMask := syscall.Umask(tempMask)
|
||||
syscall.Umask(oldMask)
|
||||
return fs.FileMode(oldMask)
|
||||
}
|
||||
9
internal/config/umask_windows.go
Normal file
9
internal/config/umask_windows.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
func getCurrentUmask() (mask fs.FileMode) {
|
||||
return 0
|
||||
}
|
||||
@@ -34,19 +34,19 @@ type commonSettings struct {
|
||||
// JSONProviders obtain the update settings from the JSON content,
|
||||
// first trying from the environment variable CONFIG and then from
|
||||
// the file config.json.
|
||||
func (r *Reader) JSONProviders(filePath string) (
|
||||
func (r *Reader) JSONProviders(filePath string, umask fs.FileMode) (
|
||||
providers []provider.Provider, warnings []string, err error) {
|
||||
providers, warnings, err = r.getProvidersFromEnv(filePath)
|
||||
providers, warnings, err = r.getProvidersFromEnv(filePath, umask)
|
||||
if providers != nil || warnings != nil || err != nil {
|
||||
return providers, warnings, err
|
||||
}
|
||||
return r.getProvidersFromFile(filePath)
|
||||
return r.getProvidersFromFile(filePath, umask)
|
||||
}
|
||||
|
||||
var errWriteConfigToFile = errors.New("cannot write configuration to file")
|
||||
|
||||
// getProvidersFromFile obtain the update settings from config.json.
|
||||
func (r *Reader) getProvidersFromFile(filePath string) (
|
||||
func (r *Reader) getProvidersFromFile(filePath string, umask fs.FileMode) (
|
||||
providers []provider.Provider, warnings []string, err error) {
|
||||
r.logger.Info("reading JSON config from file " + filePath)
|
||||
bytes, err := r.readFile(filePath)
|
||||
@@ -57,9 +57,8 @@ func (r *Reader) getProvidersFromFile(filePath string) (
|
||||
|
||||
r.logger.Info("file not found, creating an empty settings file")
|
||||
|
||||
const mode = fs.FileMode(0600)
|
||||
|
||||
err = r.writeFile(filePath, []byte(`{}`), mode)
|
||||
filePerm := fs.FileMode(0o666) - umask //nolint:gomnd
|
||||
err = r.writeFile(filePath, []byte(`{}`), filePerm)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%w: %w", errWriteConfigToFile, err)
|
||||
}
|
||||
@@ -72,7 +71,7 @@ func (r *Reader) getProvidersFromFile(filePath string) (
|
||||
|
||||
// getProvidersFromEnv obtain the update settings from the environment variable CONFIG.
|
||||
// If the settings are valid, they are written to the filePath.
|
||||
func (r *Reader) getProvidersFromEnv(filePath string) (
|
||||
func (r *Reader) getProvidersFromEnv(filePath string, umask fs.FileMode) (
|
||||
providers []provider.Provider, warnings []string, err error) {
|
||||
s := os.Getenv("CONFIG")
|
||||
if s == "" {
|
||||
@@ -93,8 +92,8 @@ func (r *Reader) getProvidersFromEnv(filePath string) (
|
||||
if err != nil {
|
||||
return providers, warnings, fmt.Errorf("%w: %w", errWriteConfigToFile, err)
|
||||
}
|
||||
const mode = fs.FileMode(0600)
|
||||
err = r.writeFile(filePath, buffer.Bytes(), mode)
|
||||
filePerm := fs.FileMode(0o666) - umask //nolint:gomnd
|
||||
err = r.writeFile(filePath, buffer.Bytes(), filePerm)
|
||||
if err != nil {
|
||||
return providers, warnings, fmt.Errorf("%w: %w", errWriteConfigToFile, err)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type Database struct {
|
||||
data dataModel
|
||||
filepath string
|
||||
mutex sync.RWMutex
|
||||
umask fs.FileMode
|
||||
}
|
||||
|
||||
func (db *Database) Close() error {
|
||||
@@ -26,7 +27,7 @@ func (db *Database) Close() error {
|
||||
}
|
||||
|
||||
// NewDatabase opens or creates the JSON file database.
|
||||
func NewDatabase(dataDir string) (*Database, error) {
|
||||
func NewDatabase(dataDir string, umask fs.FileMode) (*Database, error) {
|
||||
filePath := filepath.Join(dataDir, "updates.json")
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
@@ -34,8 +35,8 @@ func NewDatabase(dataDir string) (*Database, error) {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
const perm fs.FileMode = 0700
|
||||
err = os.MkdirAll(filepath.Dir(filePath), perm)
|
||||
dirPerm := os.FileMode(0o777) - umask //nolint:gomnd
|
||||
err = os.MkdirAll(filepath.Dir(filePath), dirPerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating data directory: %w", err)
|
||||
}
|
||||
@@ -80,6 +81,7 @@ func NewDatabase(dataDir string) (*Database, error) {
|
||||
return &Database{
|
||||
data: data,
|
||||
filepath: filePath,
|
||||
umask: umask,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -134,8 +136,8 @@ func checkHistoryEvents(events []models.HistoryEvent) (err error) {
|
||||
}
|
||||
|
||||
func (db *Database) write() error {
|
||||
const createPerms fs.FileMode = 0600
|
||||
file, err := os.OpenFile(db.filepath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, createPerms)
|
||||
filePerm := os.FileMode(0o666) - db.umask //nolint:gomnd
|
||||
file, err := os.OpenFile(db.filepath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, filePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening file: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user