Files
uopi/uopi.example.toml
2026-06-24 06:12:52 +02:00

183 lines
9.1 KiB
TOML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[server]
listen = ":8080"
storage_dir = "./interfaces"
# ── UI presentation defaults (sent to the browser) ──────────────────────────
# default_zoom is the base UI scale used when a browser has no saved zoom of its
# own. Handy for HiDPI screens (e.g. 4K) whose OS scaling is left at 100%, where
# the browser reports devicePixelRatio=1 and the UI would otherwise be tiny. The
# in-app A+/A control still overrides it per browser. 1.0 = no scaling.
[ui]
default_zoom = 1.0 # e.g. 1.5 or 1.75 on dense displays
# ── Identity & access control ───────────────────────────────────────────────
# The end-user identity is read from a header set by a trusted authenticating
# reverse proxy. Leave trusted_user_header empty for unproxied/dev/LAN use.
# SECURITY: only enable this when the proxy strips any client-supplied value of
# this header, otherwise it can be spoofed.
trusted_user_header = "" # e.g. "X-Forwarded-User"
# Identity used when the trusted header is absent/empty. Empty = anonymous.
default_user = ""
# ── Native Kerberos / SPNEGO authentication ─────────────────────────────────
# Alternative to a reverse proxy: uopi authenticates browsers directly via
# SPNEGO ("Negotiate"). Recommended when proxy-header auth lets some browsers
# (notably Firefox) silently fall through to default_user. When enabled, uopi
# challenges API requests with 401 WWW-Authenticate: Negotiate and resolves the
# user from the validated Kerberos ticket (short principal name, realm stripped);
# any inbound trusted_user_header value is ignored to prevent spoofing.
# Browsers must be told to perform SPNEGO for this origin
# (Firefox: network.negotiate-auth.trusted-uris).
[server.kerberos]
enabled = false
# Path to the service keytab holding the HTTP service principal's key
# (e.g. HTTP/host.example.com@REALM). Required when enabled.
keytab = "" # e.g. "/etc/uopi/http.keytab"
# Optionally pin which keytab principal to accept tickets for. Empty = default.
service_principal = "" # e.g. "HTTP/host.example.com"
# ── Built-in HTTP Basic authentication (PAM) ────────────────────────────────
# Another standalone alternative (no reverse proxy, no Kerberos): uopi challenges
# with 401 WWW-Authenticate: Basic and validates the username/password through the
# host PAM stack (/etc/pam.d/<pam_service>). On an SSSD/LDAP-joined host this
# reuses each user's normal login password with no directory config in uopi. The
# validated username feeds the same access pipeline. Mutually exclusive with
# Kerberos. REQUIRES a PAM-enabled build: `make backend-pam` (cgo + libpam); the
# default static binary cannot validate and refuses to start with this enabled.
# Basic credentials travel on every request — enable [server.tls] below unless on
# a fully isolated network.
[server.basic_auth]
enabled = false
# PAM service name under /etc/pam.d/. Empty defaults to "uopi".
pam_service = "" # e.g. "uopi" or "login"
# ── Built-in HTTP Basic authentication (LDAP) ───────────────────────────────
# Pure-Go alternative to basic_auth/PAM: validates the Basic credentials directly
# against an LDAP directory using "search then bind" — the same flow an SSSD/LDAP
# client uses. Needs NO cgo, so it works in the default fully-static binary
# (`make release`), unlike PAM. Mutually exclusive with kerberos and basic_auth.
# Defaults mirror SSSD: user_attr → "uid", user_object_class → "posixAccount",
# empty bind_dn → anonymous search. Enable TLS below (or use ldaps://) so
# passwords are not sent in clear text.
[server.ldap]
enabled = false
# Directory endpoint(s), tried in order (SSSD ldap_uri).
uri = ["ldaps://ldap.example.com"]
# Subtree user entries live under (SSSD ldap_search_base).
search_base = "dc=example,dc=com"
# Attribute matched against the login name. Empty defaults to "uid".
user_attr = "" # e.g. "uid" (RFC2307) or "sAMAccountName" (AD)
# Object class to restrict the search. Empty defaults to "posixAccount".
user_object_class = "" # e.g. "posixAccount" or "user" (AD)
# Optional service account for the search. Empty bind_dn = anonymous search.
bind_dn = ""
bind_password = ""
# Upgrade a plain ldap:// connection to TLS before binding (ignored for ldaps://).
start_tls = false
# Optional PEM CA bundle to trust a private directory CA.
ca_cert = "" # e.g. "/etc/uopi/ldap-ca.pem"
# Disable TLS certificate verification (testing only — insecure).
insecure_skip_verify = false
# ── Built-in TLS / HTTPS ────────────────────────────────────────────────────
# Terminate HTTPS directly without a reverse proxy. Strongly recommended whenever
# basic_auth is enabled. Cert and key are PEM paths; both required when enabled.
[server.tls]
enabled = false
cert = "" # e.g. "/etc/uopi/tls/cert.pem"
key = "" # e.g. "/etc/uopi/tls/key.pem"
# Optionally run a plain-HTTP listener that 301-redirects visitors to the HTTPS
# service, so users who type http:// (or omit the scheme) are upgraded instead of
# hitting the TLS port with a cleartext request ("client sent an HTTP request to
# an HTTPS server"). Empty = disabled.
redirect_from = "" # e.g. ":80"
# Role-based access through group memberships. Each [[groups]] block lists
# members by role; a user's effective global capability is the highest role
# across all their memberships, along this ladder:
# viewer < operator < logiceditor < auditor < admin
# Capabilities: operator+ may write signals; logiceditor+ may edit panel and
# control logic; auditor+ may view the audit log; admin may open the admin pane.
#
# The built-in "public" group is implicit (every user is a viewer of it). A
# config with NO roles assigned anywhere is fully open (everyone is admin), for
# trusted-LAN/dev use; assigning any role switches to strict mode where unlisted
# users are read-only viewers. Once changed at runtime via the admin pane,
# {storage_dir}/access.json supersedes the groups below.
#
# Groups may nest via "parent": a member of a parent group inherits its role on
# every descendant group too (unless overridden lower).
# [[groups]]
# name = "public"
# admins = ["alice"] # alice is a global admin
# [[groups]]
# name = "operations"
# operators = ["bob"]
# auditors = ["carol"]
# [[groups]]
# name = "engineers"
# parent = "operations" # bob inherits operator here
# logiceditors = ["dave"]
# viewers = ["erin"]
[datasource.epics]
enabled = true
ca_addr_list = "" # overrides EPICS_CA_ADDR_LIST if set
archive_url = "" # EPICS Archive Appliance base URL
channel_finder_url = "" # e.g. http://channelfinder:8080/ChannelFinder
auto_sync_filter = "" # e.g. area=StorageRing or tags=production
[datasource.synthetic]
enabled = true
# Modbus TCP. Each device is polled over its own connection; registers become
# signals named "device:register". Disabled unless enabled = true and at least
# one device is declared.
[datasource.modbus]
enabled = false
poll_interval_ms = 1000 # default poll period for every register
# [[datasource.modbus.devices]]
# name = "plc1"
# address = "192.168.1.50:502" # ":502" appended if no port given
# unit_id = 1
# timeout_ms = 3000
#
# [[datasource.modbus.devices.registers]]
# name = "temperature"
# kind = "holding" # holding | input | coil | discrete
# address = 100
# encoding = "int16" # uint16|int16|uint32|int32|float32|float64
# word_order = "big" # big (default) | little, for multi-word encodings
# unit = "C"
# scale = 0.1 # value = raw*scale + offset (scale 0 → 1)
# offset = 0.0
# min = -50
# max = 200
# writable = false # input/discrete are always read-only
# description = "Process temperature"
# SCPI instruments over raw TCP sockets (line-based "SCPI raw", port 5025).
# Channels become signals named "instrument:channel". A vxi11 transport is
# reserved for a future release.
[datasource.scpi]
enabled = false
poll_interval_ms = 1000
# [[datasource.scpi.instruments]]
# name = "dmm1"
# transport = "raw" # raw (default); vxi11 reserved
# address = "192.168.1.60:5025" # ":5025" appended if no port given
# timeout_ms = 3000
# terminator = "\n"
#
# [[datasource.scpi.instruments.channels]]
# name = "voltage"
# query = "MEAS:VOLT?" # SCPI query whose response is the value
# write_cmd = "VOLT %v" # printf template; omit for read-only
# type = "float" # float (default) | int | string | bool
# unit = "V"
# min = 0
# max = 30
# description = "DC voltage"