The server has always sent the broker's CA certificate in the enrolment response, precisely so it never has to ship in an installer. Nothing on the receiving end wrote it anywhere: the agent read the field under the wrong name (ca_pem, the server says ca_cert) and the desktop app read it correctly and dropped it. Every claimed PC therefore dialled tls://mcp.loyaly.ai:8883 with the system trust store, the private CA failed verification, and the agent reported 'the broker did not accept this PC' - a TLS failure is indistinguishable from a refusal at that layer. No real site could ever have published a visit. Found by claiming this Mac as a real shop against production; fixed by writing the CA to broker-ca.crt beside agent.json on both claim paths. Verified: broker connected over TLS, camera pushed from head office, engine streaming it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
83 lines
2.5 KiB
Go
83 lines
2.5 KiB
Go
// Package paths mirrors behavision/paths.py.
|
|
//
|
|
// The two processes must agree on where state lives or they will quietly use
|
|
// different databases: the engine would write footfall into one file while the
|
|
// agent reads another and reports an empty store. The rule is the same on both
|
|
// sides — BEHAVISION_DATA_DIR wins, then %PROGRAMDATA%\Behavision on Windows —
|
|
// and `behavision paths` prints the engine's answer so the two can be compared
|
|
// on a real machine rather than assumed equal.
|
|
package paths
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
const AppName = "Behavision"
|
|
|
|
// StateRoot is the writable root: database, logs, spool, agent config.
|
|
func StateRoot() string {
|
|
if v := os.Getenv("BEHAVISION_DATA_DIR"); v != "" {
|
|
if abs, err := filepath.Abs(v); err == nil {
|
|
return abs
|
|
}
|
|
return v
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
base := os.Getenv("PROGRAMDATA")
|
|
if base == "" {
|
|
base = `C:\ProgramData`
|
|
}
|
|
return filepath.Join(base, AppName)
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "."
|
|
}
|
|
if runtime.GOOS == "darwin" {
|
|
return filepath.Join(home, "Library", "Application Support", AppName)
|
|
}
|
|
if v := os.Getenv("XDG_DATA_HOME"); v != "" {
|
|
return filepath.Join(v, "behavision")
|
|
}
|
|
return filepath.Join(home, ".local", "share", "behavision")
|
|
}
|
|
|
|
// InstallRoot is the directory holding this executable.
|
|
func InstallRoot() string {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "."
|
|
}
|
|
if resolved, err := filepath.EvalSymlinks(exe); err == nil {
|
|
exe = resolved
|
|
}
|
|
return filepath.Dir(exe)
|
|
}
|
|
|
|
func AgentConfig() string { return filepath.Join(StateRoot(), "agent.json") }
|
|
|
|
// BrokerCA is the broker's CA certificate, written at enrolment.
|
|
func BrokerCA() string { return filepath.Join(StateRoot(), "broker-ca.crt") }
|
|
func SpoolDir() string { return filepath.Join(StateRoot(), "spool") }
|
|
func EngineLog() string { return filepath.Join(StateRoot(), "engine.log") }
|
|
|
|
// APICredentials is the file the engine writes when it generates its own
|
|
// Basic credentials. The agent reads it rather than storing a second copy,
|
|
// so a regenerated credential does not silently break the tray.
|
|
func APICredentials() string {
|
|
return filepath.Join(StateRoot(), "data", "api_credentials.txt")
|
|
}
|
|
|
|
// EnsureState creates the writable tree. Called before anything opens a file
|
|
// under it, so a first run on a fresh machine does not fail on a missing dir.
|
|
func EnsureState() error {
|
|
for _, d := range []string{StateRoot(), SpoolDir()} {
|
|
if err := os.MkdirAll(d, 0o700); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|