Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
|
|
"sourcedock.dev/petrbalvin/interpres/v2"
|
|
)
|
|
|
|
// A tlsFile is the [tls] table of the configuration file.
|
|
type tlsFile struct {
|
|
Cert string `toml:"cert"`
|
|
Key string `toml:"key"`
|
|
}
|
|
|
|
// An exportFile is one [[export]] table of the configuration file.
|
|
type exportFile struct {
|
|
Path string `toml:"path"`
|
|
ReadOnly bool `toml:"read-only"`
|
|
RootSquash bool `toml:"root-squash"`
|
|
}
|
|
|
|
// A fileConfig mirrors the whole configuration file. Every key is
|
|
// optional; a key the file leaves out keeps the flag or the built in
|
|
// default.
|
|
type fileConfig struct {
|
|
Listen string `toml:"listen"`
|
|
LogOps bool `toml:"log-ops"`
|
|
StateDir string `toml:"state-dir"`
|
|
MaxConnections int `toml:"max-connections"`
|
|
TLS *tlsFile `toml:"tls"`
|
|
Exports []exportFile `toml:"export"`
|
|
}
|
|
|
|
// loadConfig reads and validates one configuration file. An empty path
|
|
// reads nothing and answers the zero configuration: the file is never
|
|
// read unless the operator names it.
|
|
func loadConfig(path string) (fileConfig, error) {
|
|
var cfg fileConfig
|
|
if path == "" {
|
|
return cfg, nil
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
if err := interpres.Unmarshal(data, &cfg, interpres.RejectUnknownFields(true)); err != nil {
|
|
if se, ok := errors.AsType[*interpres.SyntaxError](err); ok {
|
|
return cfg, fmt.Errorf("%s:%d:%d: %s", path, se.Line, se.Column, se.Msg)
|
|
}
|
|
return cfg, fmt.Errorf("%s: %v", path, err)
|
|
}
|
|
if len(cfg.Exports) != 1 {
|
|
return cfg, fmt.Errorf("%s: exactly one [[export]] is required, found %d", path, len(cfg.Exports))
|
|
}
|
|
if cfg.Exports[0].Path == "" {
|
|
return cfg, fmt.Errorf("%s: [[export]] names no path", path)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// applyConfig folds the file configuration under the flags: the file is
|
|
// the base, every flag the operator passed overrides it. The visited set
|
|
// is the flags present on the command line, defaults included.
|
|
func applyConfig(cfg fileConfig, fs *flag.FlagSet, set func(string) bool) {
|
|
if v := cfg.Listen; v != "" && !set("addr") {
|
|
_ = fs.Set("addr", v)
|
|
}
|
|
if len(cfg.Exports) == 1 && !set("export") {
|
|
_ = fs.Set("export", cfg.Exports[0].Path)
|
|
if cfg.Exports[0].ReadOnly && !set("ro") {
|
|
_ = fs.Set("ro", "true")
|
|
}
|
|
if cfg.Exports[0].RootSquash && !set("root-squash") {
|
|
_ = fs.Set("root-squash", "true")
|
|
}
|
|
}
|
|
if cfg.LogOps && !set("log-ops") {
|
|
_ = fs.Set("log-ops", "true")
|
|
}
|
|
if v := cfg.StateDir; v != "" && !set("state-dir") {
|
|
_ = fs.Set("state-dir", v)
|
|
}
|
|
if v := cfg.MaxConnections; v != 0 && !set("max-connections") {
|
|
_ = fs.Set("max-connections", fmt.Sprint(v))
|
|
}
|
|
if cfg.TLS != nil && !set("tls-cert") && !set("tls-key") {
|
|
if cfg.TLS.Cert != "" {
|
|
_ = fs.Set("tls-cert", cfg.TLS.Cert)
|
|
}
|
|
if cfg.TLS.Key != "" {
|
|
_ = fs.Set("tls-key", cfg.TLS.Key)
|
|
}
|
|
}
|
|
}
|