189 lines
5.9 KiB
Go
189 lines
5.9 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
type Config struct {
|
|
Server ServerConfig `toml:"server"`
|
|
Datasource DatasourceConfig `toml:"datasource"`
|
|
Audit AuditConfig `toml:"audit"`
|
|
// Groups are named sets of users, referenced by panel sharing rules.
|
|
Groups []GroupDef `toml:"groups"`
|
|
}
|
|
|
|
// AuditConfig controls the audit trail. When enabled, every user and automated
|
|
// action that could affect the controlled system (signal writes, control-logic
|
|
// changes) is recorded to a SQLite database for later review by audit staff.
|
|
type AuditConfig struct {
|
|
Enabled bool `toml:"enabled"`
|
|
DBPath string `toml:"db_path"` // SQLite file; default {storage_dir}/audit.db
|
|
// Readers lists users or group names allowed to view the audit log. Empty
|
|
// leaves viewing unrestricted (any caller with read access). Anonymous /
|
|
// trusted-LAN callers are always permitted.
|
|
Readers []string `toml:"readers"`
|
|
}
|
|
|
|
// GroupDef is a named set of users defined as [[groups]] in the config file.
|
|
type GroupDef struct {
|
|
Name string `toml:"name"`
|
|
Members []string `toml:"members"`
|
|
}
|
|
|
|
// BlacklistEntry downgrades a specific user's global access level. Levels:
|
|
// "readonly" (no writes) or "noaccess" (denied). Users not listed are trusted
|
|
// with full access.
|
|
type BlacklistEntry struct {
|
|
User string `toml:"user"`
|
|
Level string `toml:"level"`
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Listen string `toml:"listen"`
|
|
StorageDir string `toml:"storage_dir"`
|
|
MaxUpdateRateHz float64 `toml:"max_update_rate_hz"` // 0 = unlimited
|
|
|
|
// TrustedUserHeader is the HTTP header from which the end-user identity is
|
|
// read on each WebSocket connection (set by a trusted reverse proxy doing
|
|
// authentication). The identity is used for EPICS writes so each session
|
|
// can act as a different user. Empty disables it (writes use the server
|
|
// identity). MUST only be enabled when a proxy strips any client-supplied
|
|
// value, otherwise the header can be spoofed.
|
|
TrustedUserHeader string `toml:"trusted_user_header"`
|
|
|
|
// DefaultUser is the identity used when the trusted user header is absent or
|
|
// empty (e.g. unproxied/dev/LAN deployments). Empty leaves the user anonymous.
|
|
DefaultUser string `toml:"default_user"`
|
|
|
|
// Blacklist downgrades specific users' global access level. Everyone not
|
|
// listed is trusted with full write access.
|
|
Blacklist []BlacklistEntry `toml:"blacklist"`
|
|
|
|
// LogicEditors optionally restricts who may add or edit panel logic (the
|
|
// <logic> block of interfaces) and server-side control logic. Entries are
|
|
// usernames or group names. When empty, no restriction applies (any user
|
|
// with write access may edit logic). Anonymous/trusted-LAN callers are
|
|
// always permitted.
|
|
LogicEditors []string `toml:"logic_editors"`
|
|
}
|
|
|
|
type DatasourceConfig struct {
|
|
Stub StubConfig `toml:"stub"`
|
|
EPICS EPICSConfig `toml:"epics"`
|
|
PVA PVAConfig `toml:"pva"`
|
|
Synthetic SyntheticConfig `toml:"synthetic"`
|
|
}
|
|
|
|
type StubConfig struct {
|
|
Enabled bool `toml:"enabled"`
|
|
}
|
|
|
|
type EPICSConfig struct {
|
|
Enabled bool `toml:"enabled"`
|
|
CAAddrList string `toml:"ca_addr_list"`
|
|
ArchiveURL string `toml:"archive_url"`
|
|
ChannelFinderURL string `toml:"channel_finder_url"`
|
|
AutoSyncFilter string `toml:"auto_sync_filter"`
|
|
AutoSyncFromArchiver bool `toml:"auto_sync_from_archiver"`
|
|
PVNames []string `toml:"pv_names"`
|
|
}
|
|
|
|
type PVAConfig struct {
|
|
Enabled bool `toml:"enabled"`
|
|
AddrList []string `toml:"addr_list"`
|
|
}
|
|
|
|
type SyntheticConfig struct {
|
|
Enabled bool `toml:"enabled"`
|
|
}
|
|
|
|
func Default() Config {
|
|
return Config{
|
|
Server: ServerConfig{
|
|
Listen: ":8080",
|
|
StorageDir: "./interfaces",
|
|
},
|
|
Datasource: DatasourceConfig{
|
|
Stub: StubConfig{Enabled: true},
|
|
EPICS: EPICSConfig{Enabled: true},
|
|
PVA: PVAConfig{Enabled: true},
|
|
Synthetic: SyntheticConfig{Enabled: true},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Load reads a TOML config file and applies environment variable overrides.
|
|
// Environment variables use the prefix UOPI_ with __ as separator, e.g.
|
|
// UOPI_SERVER_LISTEN=":9090".
|
|
func Load(path string) (Config, error) {
|
|
cfg := Default()
|
|
|
|
if path != "" {
|
|
if _, err := toml.DecodeFile(path, &cfg); err != nil {
|
|
return cfg, fmt.Errorf("reading config %q: %w", path, err)
|
|
}
|
|
}
|
|
|
|
applyEnv(&cfg)
|
|
return cfg, nil
|
|
}
|
|
|
|
func applyEnv(cfg *Config) {
|
|
if v := env("UOPI_SERVER_LISTEN"); v != "" {
|
|
cfg.Server.Listen = v
|
|
}
|
|
if v := env("UOPI_SERVER_STORAGE_DIR"); v != "" {
|
|
cfg.Server.StorageDir = v
|
|
}
|
|
if v := env("UOPI_SERVER_MAX_UPDATE_RATE_HZ"); v != "" {
|
|
if hz, err := strconv.ParseFloat(v, 64); err == nil {
|
|
cfg.Server.MaxUpdateRateHz = hz
|
|
}
|
|
}
|
|
if v := env("UOPI_SERVER_TRUSTED_USER_HEADER"); v != "" {
|
|
cfg.Server.TrustedUserHeader = v
|
|
}
|
|
if v := env("UOPI_SERVER_DEFAULT_USER"); v != "" {
|
|
cfg.Server.DefaultUser = v
|
|
}
|
|
if v := env("UOPI_SERVER_LOGIC_EDITORS"); v != "" {
|
|
cfg.Server.LogicEditors = strings.Fields(v)
|
|
}
|
|
if v := env("UOPI_AUDIT_ENABLED"); v != "" {
|
|
cfg.Audit.Enabled = (v == "true" || v == "YES")
|
|
}
|
|
if v := env("UOPI_AUDIT_DB_PATH"); v != "" {
|
|
cfg.Audit.DBPath = v
|
|
}
|
|
if v := env("UOPI_AUDIT_READERS"); v != "" {
|
|
cfg.Audit.Readers = strings.Fields(v)
|
|
}
|
|
if v := env("UOPI_EPICS_CA_ADDR_LIST"); v != "" {
|
|
cfg.Datasource.EPICS.CAAddrList = v
|
|
}
|
|
if v := env("UOPI_EPICS_ARCHIVE_URL"); v != "" {
|
|
cfg.Datasource.EPICS.ArchiveURL = v
|
|
}
|
|
if v := env("UOPI_EPICS_CHANNEL_FINDER_URL"); v != "" {
|
|
cfg.Datasource.EPICS.ChannelFinderURL = v
|
|
}
|
|
if v := env("UOPI_EPICS_AUTO_SYNC_FILTER"); v != "" {
|
|
cfg.Datasource.EPICS.AutoSyncFilter = v
|
|
}
|
|
if v := env("UOPI_EPICS_AUTO_SYNC_FROM_ARCHIVER"); v != "" {
|
|
cfg.Datasource.EPICS.AutoSyncFromArchiver = (v == "true" || v == "YES")
|
|
}
|
|
if v := env("EPICS_PVA_ADDR_LIST"); v != "" {
|
|
cfg.Datasource.PVA.AddrList = strings.Fields(v)
|
|
}
|
|
}
|
|
|
|
func env(key string) string {
|
|
return strings.TrimSpace(os.Getenv(key))
|
|
}
|