package config import ( "bufio" "os" "strings" ) // EngineCredentials reads the Basic credentials the engine generated for // itself, from the file it writes them to. // // The engine only invents a credential when none is configured and its API // listens on a routable address - which is the DEFAULT configuration, so this // is the ordinary case and not an edge one. `paths.APICredentials` has existed // since the agent was written, with a comment saying the agent reads the file // "rather than storing a second copy, so a regenerated credential does not // silently break the tray". Nothing read it. On a stock install the agent's // api_user was therefore empty and every call it makes to the engine - health, // stats, camera sync, embeddings for a visit - came back 401: the tray red, the // cameras never reconciled, and no error anywhere saying why. // // A missing or unreadable file is not an error. A PC where the operator set // BEHAVISION_API_USER has no such file and needs none. func EngineCredentials(path string) (user, password string) { f, err := os.Open(path) if err != nil { return "", "" } defer f.Close() sc := bufio.NewScanner(f) for sc.Scan() { // `key=value`, and `key: value` too: the file is also read by people, // and which separator the engine used is not worth a support call. line := strings.TrimSpace(sc.Text()) k, v, ok := strings.Cut(line, "=") if !ok { k, v, ok = strings.Cut(line, ":") } if !ok { continue } switch strings.TrimSpace(k) { case "username": user = strings.TrimSpace(v) case "password": password = strings.TrimSpace(v) } } return user, password } // WithEngineCredentials fills in the engine's Basic credentials from the file // when the config carries none. Configured values always win: an operator who // set BEHAVISION_API_USER means it. func (c Config) WithEngineCredentials(path string) Config { if c.APIUser != "" || c.APIPassword != "" { return c } c.APIUser, c.APIPassword = EngineCredentials(path) return c }