Documentation
¶
Index ¶
- Constants
- func ConnectDatabase(config *ConfigDB) (*gorm.DB, error)
- func FactumHTTP(cfg *ConfigFactum) (client *http.Client, baseURL string, viaSocket bool, err error)
- func FetchRemoteConfig[T any](factumConfig *ConfigFactum, path string) (*T, error)
- func FormatName(defaultDomain string, name string) string
- func GetOrCreateSettings(db *gorm.DB) (*models.Settings, error)
- func HubSocketPath(yamlOverride string) string
- func MigrateDatabase(db *gorm.DB) error
- func OrganizationEnabled(db *gorm.DB) bool
- func OxidizedEnabled(db *gorm.DB) bool
- func Pprint(data any)
- func ShortName(name, defaultDomain string) string
- func StructToString(data any) string
- type CommonConfig
- type ConfigAgentRoot
- type ConfigDB
- type ConfigDNS
- type ConfigDeviceSync
- type ConfigDeviceSyncAuth
- type ConfigDriver
- type ConfigFactum
- type ConfigIcinga
- type ConfigIcingaCert
- type ConfigLdapWriteback
- type ConfigLibrenms
- type ConfigNetbox
- type ConfigOxidized
- type ConfigPrometheus
- type ConfigRoot
- type ConfigStorage
- type ConfigWeb
- type ConfigWorker
- type ConfigWorkerCommand
- type Response
Constants ¶
const DefaultHubSocket = "/run/factum2-worker/api.sock"
DefaultHubSocket is the unix HTTP path factum2-worker listens on and co-located CLIs probe. One constant so the two sides cannot drift.
const HubRPCTimeout = 60 * time.Second
HubRPCTimeout is the per-request bound for hub RPC (ServeHTTP after the websocket dies, CLI HTTP clients). Canonical here so util does not import worker.
Variables ¶
This section is empty.
Functions ¶
func ConnectDatabase ¶
ConnectDatabase opens Postgres and does not apply schema migrations. Call this from the web server and CLIs. Schema changes belong in the dedicated `migrate` command (cmdbase.Migrate → MigrateDatabase); running migrations as a side effect of start/sync/createadmin rewrites tables while factum2-web may already be serving.
func FactumHTTP ¶ added in v1.0.2
FactumHTTP picks a client for Factum API calls. The local worker unix socket is used when it is present and dialable; otherwise HTTPS to cfg.URL. After a successful unix Dial, RPC 502s/timeouts are not retried over HTTPS - worker-net :443 may already be closed. Unix requests send no Authorization header (the socket ACL is the auth); HTTPS keeps the bearer.
func FetchRemoteConfig ¶
func FetchRemoteConfig[T any](factumConfig *ConfigFactum, path string) (*T, error)
FetchRemoteConfig GETs path from the Factum API and unmarshals the JSON into a new T. Transport is selected by FactumHTTP (unix socket when the probe succeeds, otherwise HTTPS to factumConfig.URL with "Authorization: Bearer "+factumConfig.Token - see web.Controller.checkServiceToken). A unix 502 is returned as an error; it is not retried over HTTPS. Shared by CLI tools that typically run on a different host than the primary (factum2-icinga, factum2-oxidized, factum2-dns, factum2-librenms-cli, factum2-prometheus) to fetch their database-backed settings instead of needing a local copy - each package wraps this in its own FetchRemoteConfig returning its own config type, see e.g. internal/librenms/remote_config.go.
func FormatName ¶
FormatName appends defaultDomain when name has no '.' (a short hostname). Names that are already FQDNs (or IPv4 literals) are returned unchanged. An empty defaultDomain is a no-op so callers don't get a trailing dot.
func GetOrCreateSettings ¶
GetOrCreateSettings returns the single Settings row (id=1), creating it with zero-valued fields on first use so callers always have a row to read - most settings (integration URLs/tokens like Netbox's, previously only configurable via the YAML config file) are now database-backed and editable from the admin UI instead, so both the web API and any CLI/sync code that needs them goes through here.
func HubSocketPath ¶ added in v1.0.2
func MigrateDatabase ¶
MigrateDatabase applies schema migrations then cfgmgmt.Seed. Only cmdbase.Migrate (and tests) should call this — not ConnectDatabase.
Postgres: goose SQL in internal/dbmigrate (existing AutoMigrate DBs are stamped at the baseline version). SQLite: GORM AutoMigrateAll, for unit tests only.
func OrganizationEnabled ¶ added in v1.0.3
OrganizationEnabled reports Settings.OrganizationEnabled (nil/false = off). Disabling the flag only hides the Customers/Contacts menu — it does not touch customer or contact rows.
func OxidizedEnabled ¶ added in v1.0.4
OxidizedEnabled reports Settings.OxidizedEnabled (nil/false = off). Disabling the flag only hides the Oxidized menu and GUI browser — it does not touch device backup flags or oxidized's own router.db.
Types ¶
type CommonConfig ¶
type CommonConfig struct {
DefaultDomain string `json:"default_domain"`
// SmtpHost/Port/User/Pass/TLSMode and EmailSender are the shared
// outbound mail relay settings (Settings.Smtp*/EmailSender, edited on
// the admin UI's Factum > Email tab) - factum2-icinga-notifications
// is the first consumer, but any tool needing to send mail can reuse
// these instead of having its own copy.
SmtpHost string `json:"smtp_host"`
SmtpPort uint16 `json:"smtp_port"`
SmtpUser string `json:"smtp_user"`
SmtpPass string `json:"smtp_pass"`
SmtpTLSMode string `json:"smtp_tls_mode"`
EmailSender string `json:"email_sender"`
}
CommonConfig holds settings shared by every remote-config-fetching CLI tool, not just one of them: DefaultDomain (Settings.DefaultDomain, edited in the admin UI's Factum tab) is needed by DNS (the $ORIGIN of the generated zone file) as well as Icinga/LibreNMS/Oxidized/Prometheus sync (matching factum device names against fully-qualified DNS names).
Every service's REST response and runtime Config type embeds this instead of each declaring its own copy of the fields. Tools that don't need any other service-specific config (currently just factum2-worker agents) can fetch this alone from web.ApiCommonConfig (GET /api/common-config).
func NewCommonConfig ¶
func NewCommonConfig(settings *models.Settings) CommonConfig
NewCommonConfig builds a CommonConfig from the Settings row. Every web.ApiXxxConfig handler uses this instead of repeating the same lines.
type ConfigAgentRoot ¶
type ConfigAgentRoot struct {
Factum ConfigFactum `yaml:"factum"`
Worker ConfigWorker `yaml:"worker"`
Driver ConfigDriver `yaml:"driver" optional:"true"`
Storage ConfigStorage `yaml:"storage" optional:"true"`
}
Agents get most of their configuration from the Factum API - factum2-worker is the one exception, needing its own local Worker section (worker.listen/ .token/.roles/.commands - see the comment on ConfigWorker for why these must stay local rather than fetched remotely).
type ConfigDNS ¶
type ConfigDNS struct {
CommonConfig
DestFile string
IgnoreModels string
IgnorePlatforms string
}
ConfigDNS is a runtime-only DTO, not part of ConfigRoot - same pattern as ConfigIcinga: every field has a DB-backed equivalent (Settings.DnsDestFile/DnsIgnoreModels/DnsIgnorePlatforms, plus Settings.DefaultDomain via the embedded CommonConfig), fetched over REST by internal/dns.FetchRemoteConfig/RemoteClient from web.ApiDNSConfig.
type ConfigDeviceSync ¶
type ConfigDeviceSync struct {
CommonConfig
VRFInGlobal []string
DeviceStates []string
DeviceIgnore []string
// VlanGroupName is the Netbox VLAN Group every synced VLAN is created
// in (Settings.DeviceSyncVlanGroupName) - "" disables VLAN sync.
VlanGroupName string
// InventoryMaps is sync_source → netbox_type from cfgmgmt service
// types (ELINE→evpl, ELAN→vpls, L3VPN→vrf). Empty means device-sync
// falls back to ELINE→evpl only.
InventoryMaps map[string]string
Auth map[string]ConfigDeviceSyncAuth
}
ConfigDeviceSync is a runtime-only DTO, not part of ConfigRoot - same pattern as ConfigNetbox/ConfigOxidized: every scalar-ish field has a DB-backed equivalent (Settings.DeviceSyncVRFInGlobal/DeviceStates/ DeviceIgnore, all newline-separated text; Auth comes from the models.DeviceSyncAuth table instead of a Settings column, since it's a list of credentials, not a single value), fetched over REST by internal/device-sync.FetchRemoteConfig from web.ApiDeviceSyncConfig. factum2-device-sync-cli runs on the primary and talks to factum2-web over REST (util.WithoutHubSocket), not the hub unix socket. The Netbox client itself isn't fetched here - internal/netbox.RemoteClient/FetchRemoteConfig already does that (GET /api/netbox-config), and device-sync reuses it rather than duplicating Netbox credentials in a second config type.
type ConfigDeviceSyncAuth ¶
ConfigDeviceSyncAuth is the username/password internal/device-sync uses to log into a device - keyed by device name in ConfigDeviceSync.Auth, with a "default" entry as the fallback for devices without their own entry.
type ConfigDriver ¶ added in v1.1.1
type ConfigDriver struct {
Platforms *[]string `boa:"configonly" yaml:"platforms" optional:"true"`
IdleTimeout string `boa:"configonly" yaml:"idle_timeout" optional:"true"`
MaxSessions int `boa:"configonly" yaml:"max_sessions" optional:"true"`
QueueDepth int `boa:"configonly" yaml:"queue_depth" optional:"true"`
AcquireTimeout string `boa:"configonly" yaml:"acquire_timeout" optional:"true"`
Keepalive string `boa:"configonly" yaml:"keepalive" optional:"true"`
SessionURL string `boa:"configonly" yaml:"session_url" optional:"true"`
SessionToken string `boa:"configonly" yaml:"session_token" optional:"true"`
Socket string `boa:"configonly" yaml:"socket" optional:"true"`
Listen string `boa:"configonly" yaml:"listen" optional:"true"`
Token string `boa:"configonly" yaml:"token" optional:"true"`
TLSCert string `boa:"configonly" yaml:"tls_cert" optional:"true"`
TLSKey string `boa:"configonly" yaml:"tls_key" optional:"true"`
TLSCA string `boa:"configonly" yaml:"tls_ca" optional:"true"`
AllowCIDRs []string `boa:"configonly" yaml:"allow_cidrs" optional:"true"`
}
type ConfigFactum ¶
type ConfigFactum struct {
// URL/Token are optional at the boa level - like ConfigWorker.Roles,
// not every subcommand of every binary that embeds ConfigFactum
// actually calls out to the primary (e.g. "factum2-worker start" never
// does, only "run" does, via internal/worker.RunRemote), so requiring
// them unconditionally would force values into a config file that
// aren't needed for that particular subcommand. Call sites that do
// need them (RunRemote, internal/factum.FactumClient) check for an
// empty URL/Token themselves and fail with a clear error instead.
URL string `boa:"configonly" yaml:"url" optional:"true"`
// Token authenticates server-to-server callers (internal/factum's HTTP
// client, used by e.g. factum2-dns and factum2-librenms-cli, the latter
// typically running on a different host than the primary) against the
// primary's Settings.FactumApiToken, sent as "Authorization: Bearer
// <token>". See web.Controller.RequireAPIAuth.
Token string `boa:"configonly" yaml:"token" optional:"true"`
// Socket is the local unix API path when this CLI is co-located with
// factum2-worker. Empty uses DefaultHubSocket unless FACTUM_WORKER_API_SOCKET
// overrides it; "none"/"0" disables the socket (CLI-only escape hatch).
// Primary-side CLIs should call WithoutHubSocket rather than relying on
// operators to set this.
Socket string `boa:"configonly" yaml:"socket" optional:"true"`
}
func WithoutHubSocket ¶ added in v1.0.6
func WithoutHubSocket(cfg ConfigFactum) ConfigFactum
HubSocketPath resolves the unix socket. yamlOverride is ConfigFactum.Socket (CLI) or ConfigWorker.APISocket (listener). FACTUM_WORKER_API_SOCKET is the single relocation / disable knob both sides honor when the yaml override is empty. Values "none" and "0" (yaml or env) mean "no socket" (CLI: force HTTPS; worker: Start error). WithoutHubSocket returns a copy of cfg that never probes the worker unix socket. Primary-side CLIs (device-sync and anything else that runs next to factum2-web) must use this: a co-located factum2-worker still listens on the socket, and FactumHTTP would otherwise send every call over hub RPC. Dest-host CLIs (dns, icinga, …) keep the default probe.
type ConfigIcinga ¶
type ConfigIcinga struct {
CommonConfig
URL string
Username string
Password string
HostsFile string
UsersFile string
CertsFile string
// IgnoreDevices is a newline-separated list of device names to skip
// entirely (Settings.IcingaIgnoreDevices).
IgnoreDevices string
// DefaultNotification is Jet template source executed with
// .Device for a host that has no CfAlarmDestination
// (Settings.IcingaDefaultNotification). Literal Icinga lines with no
// {{ }} still render unchanged. The result is inserted into the host
// object via hostTemplateData.Options.
DefaultNotification string
// HostTemplate/DependencyTemplate/UserTemplate/CertTemplate are Jet
// templates source, executed by internal/icinga.FactumIcingaClient -
// see that package for the data each is executed with.
HostTemplate string
DependencyTemplate string
UserTemplate string
CertTemplate string
// Certificates is the slim list used to emit Icinga cert checks
// (name, check host, DNS names). Fetched with icinga-config so the
// Icinga worker does not need ACME account secrets.
Certificates []ConfigIcingaCert
}
ConfigIcinga is a runtime-only DTO, not part of ConfigRoot - unlike ConfigLibrenms's Sync maps, every field here already has a DB-backed equivalent (Settings.IcingaApiURL/User/Pass/HostsFile/UsersFile/ CertsFile/IgnoreDevices/DefaultNotification/HostTemplate/ DependencyTemplate/UserTemplate/CertTemplate), so factum2-icinga - which typically runs on a different host than the primary - fetches this entirely over REST (internal/icinga.FetchRemoteConfig/RemoteClient, GET /api/icinga-config, served by web.ApiIcingaConfig from the Settings row) rather than reading any of it from local YAML.
type ConfigIcingaCert ¶ added in v1.1.1
type ConfigIcingaCert struct {
Name string `json:"name"`
Host string `json:"host"`
Domains []string `json:"domains"`
}
ConfigIcingaCert is one ACME certificate as Icinga needs it: the host to connect to and every name that must be presented on that host.
type ConfigLdapWriteback ¶
type ConfigLdapWriteback struct {
BindDN string `boa:"configonly" yaml:"bind_dn" optional:"true"`
Password string `boa:"configonly" yaml:"bind_password" optional:"true"`
}
ConfigLdapWriteback is the elevated LDAP/AD identity permitted to change another user's password (web.ApiForgotPassword/ApiResetPassword, web.ApiMeUpdate) - deliberately separate from Settings.LdapBindDN/ LdapBindPassword (the read-only search/bind service account, DB-stored and returned in full by GET /api/admin/settings). Password write-back needs much stronger directory permissions than a search bind, so unlike every other LDAP setting it stays config-file-only and is never exposed through the Settings API - same reasoning as ConfigWeb.JWTSecret. Both fields are optional since most installs never enable LDAP password write-back at all (Settings.LdapAllowPasswordChange defaults to off).
type ConfigLibrenms ¶
type ConfigLibrenms struct {
CommonConfig
URL string
Key string
// PersistentDevices is a newline-separated list of LibreNMS hostnames
// or display names sync never quarantines or deletes - see
// Settings.LibrenmsPersistentDevices's doc comment.
PersistentDevices string
// DelayedDeleteEnabled/DelayedDeleteDays control the LibreNMS delete
// path - see Settings.LibrenmsDelayedDeleteEnabled's doc comment.
DelayedDeleteEnabled bool
DelayedDeleteDays int
// RolesEnabled/InterfacesDisabled are newline-separated lists of
// regexes - see Settings.LibrenmsRolesEnabled's doc comment.
RolesEnabled string
InterfacesDisabled string
// SNMPVersion/SNMPCommunities are used when creating devices in
// LibreNMS - see Settings.LibrenmsSNMPVersion/LibrenmsSNMPCommunities's
// doc comments. SNMPCommunities is newline-separated, same convention
// as RolesEnabled/InterfacesDisabled.
SNMPVersion string
SNMPCommunities string
}
ConfigLibrenms is a runtime-only DTO, not part of ConfigRoot - same pattern as ConfigIcinga/ConfigDNS/ConfigOxidized: every field has a DB-backed equivalent (Settings.LibrenmsApiURL/LibrenmsApiToken/ LibrenmsPersistentDevices/LibrenmsDelayedDeleteEnabled/ LibrenmsDelayedDeleteDays/LibrenmsRolesEnabled/LibrenmsInterfacesDisabled), fetched over REST by internal/librenms.FetchRemoteConfig/RemoteClient from web.ApiLibrenmsConfig - so factum2-librenms-cli, which typically runs on a different host than the primary, doesn't need any local librenms config of its own. LibreNMS's own MySQL credentials aren't part of this struct at all - NewFactumLibrenmsClient reads those directly from LibreNMS's .env file on disk instead, since factum2-librenms-cli assumes co-location with the LibreNMS server.
type ConfigNetbox ¶
type ConfigNetbox struct {
CommonConfig
URL string
Token string
}
ConfigNetbox is a runtime-only DTO, not part of ConfigRoot - same pattern as ConfigIcinga: every field has a DB-backed equivalent (Settings.NetboxApiURL/NetboxApiToken), fetched over REST by internal/netbox.FetchRemoteConfig/RemoteClient from web.ApiNetboxConfig - so factum2-librenms-cli (which typically runs on a different host than the primary, without direct Postgres access) doesn't need its own copy of these credentials, or a direct DB connection, to build a Netbox client.
type ConfigOxidized ¶
type ConfigOxidized struct {
CommonConfig
URL string
User string
Pass string
// DestFile is oxidized's own router.db - the file
// internal/oxidized.FactumOxidizedClient.Sync writes the filtered
// device list to (name:ip:model per line).
DestFile string
// IgnoreDevices/IgnoreManufacturers/IgnoreModels/IgnorePlatforms are
// newline-separated lists (one value per line) - a device matching any
// one of them is skipped during sync.
IgnoreDevices string
IgnoreManufacturers string
IgnoreModels string
IgnorePlatforms string
}
ConfigOxidized is a runtime-only DTO, not part of ConfigRoot - same pattern as ConfigIcinga: every field has a DB-backed equivalent (Settings.OxidizedApiURL/User/Pass/DestFile/IgnoreDevices/ IgnoreManufacturers/IgnoreModels/IgnorePlatforms), fetched over REST by internal/oxidized.FetchRemoteConfig/RemoteClient from web.ApiOxidizedConfig.
type ConfigPrometheus ¶ added in v1.0.3
type ConfigPrometheus struct {
CommonConfig
DestFile string
ReloadURL string
Module string
Auth string
IgnoreDevices string
IgnoreManufacturers string
IgnoreModels string
IgnorePlatforms string
}
ConfigPrometheus is a runtime-only DTO, not part of ConfigRoot - same pattern as ConfigOxidized: every field has a DB-backed equivalent (Settings.PrometheusDestFile/ReloadURL/Module/Auth/Ignore*), fetched over REST by internal/prometheus.FetchRemoteConfig from web.ApiPrometheusConfig. factum2-prometheus typically runs on the Prometheus/snmp_exporter host, not the primary, so it has no local prometheus YAML of its own.
type ConfigRoot ¶
type ConfigRoot struct {
DB ConfigDB `yaml:"db"`
Factum ConfigFactum `yaml:"factum"`
Web ConfigWeb `yaml:"web"`
Worker ConfigWorker `yaml:"worker"`
LdapWriteback ConfigLdapWriteback `yaml:"ldap_writeback"`
Driver ConfigDriver `yaml:"driver" optional:"true"`
Storage ConfigStorage `yaml:"storage" optional:"true"`
}
Primary configuation, enough to get it up and running. most coinfig is in database
var Config *ConfigRoot
type ConfigStorage ¶ added in v1.1.1
type ConfigStorage struct {
Socket string `boa:"configonly" yaml:"socket" optional:"true"`
}
ConfigDriver is optional YAML for the SSH CLI session pool (internal/drivers). Every field is optional so existing configs keep loading. Omitted knobs and explicit 0 / "" mean compiled defaults. platforms omitted → vrp,ciscosmb; [] or [none] (or [none, ...]) turns pooling off. ConfigStorage is optional YAML for factum2-storage start (unix API socket). Listen addresses and the repository root come from Settings (GET /api/storage-config), not this file.
type ConfigWorker ¶
type ConfigWorker struct {
// Roles lists what this worker instance does. "primary" runs the
// primary loop (dispatches ad hoc commands via "factum2-worker run" and
// logs everything agents report); any other entry is the name of a
// Commands entry that this instance additionally runs as an agent for
// - so one worker process can be primary AND handle one or more
// specific agent roles (e.g. ["primary", "librenms"]) at once, instead
// of being purely one or the other. Every non-"primary" role must have
// a matching Commands entry.
//
// optional:"true" because boa's required-field check would otherwise
// treat this flat []string as required (unlike map fields, which
// default to optional) for every cmd/worker subcommand, including ones
// like "run" and "show-config" that never look at it - Worker.Start
// does its own "at least one role" check instead, only when it
// actually matters.
Roles []string `boa:"configonly" yaml:"roles" optional:"true"`
// Commands lists the predefined commands this worker is able to run as
// an agent, keyed by name - Roles selects which of these (if any) this
// particular instance actually activates. An agent only ever receives
// commands it has both defined here and activated via Roles - there is
// no separate addressing by node name.
Commands map[string]ConfigWorkerCommand `boa:"configonly" yaml:"commands"`
// Listen is the bind address (e.g. ":8443") for this agent's hub
// listener (internal/worker.runHubListener), which the primary's
// RemoteManager dials into - the reverse of this agent dialing out.
// Empty (the default) disables the listener entirely.
Listen string `boa:"configonly" yaml:"listen" optional:"true"`
// Token is the shared secret this agent expects from the primary as
// "Authorization: Bearer <Token>" on the hub connection - the primary
// side of the same value lives on the matching models.WorkerNode row,
// not in any config file.
Token string `boa:"configonly" yaml:"token" optional:"true"`
// TLSCert/TLSKey are the PEM files the hub listener serves WSS with.
// Required for factum2-worker start (hub RPC carries config secrets;
// there is no ws:// fallback). optional at boa level so show-config
// and run do not force them.
TLSCert string `boa:"configonly" yaml:"tls_cert" optional:"true"`
TLSKey string `boa:"configonly" yaml:"tls_key" optional:"true"`
// APISocket is the unix HTTP listener for hub RPC. Empty uses
// DefaultHubSocket / FACTUM_WORKER_API_SOCKET; "none"/"0" is invalid
// for factum2-worker start (fail closed). Not a route on worker.listen.
APISocket string `boa:"configonly" yaml:"api_socket" optional:"true"`
}
type ConfigWorkerCommand ¶
type ConfigWorkerCommand struct {
Cmd string `boa:"configonly" yaml:"cmd"`
Args []string `boa:"configonly" yaml:"args"`
}
ConfigWorkerCommand is one predefined command a worker agent is allowed to run. Agents only ever execute commands looked up by name from this map - the command arriving over the hub connection is never used to build a shell command directly, so a compromised/forged message can at most pick one of these predefined commands.