{"record":{"id":"9ab918351571007d","repo":"OpenNHP/opennhp","slug":"keystore-create-directory-s-w","errorCode":null,"errorMessage":"keystore: create directory %s: %w","messagePattern":"keystore: create directory (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/server/keystore.go","lineNumber":37,"sourceCode":"type AgentKeyStore struct {\n\tdb *sql.DB\n}\n\n// DefaultAgentKeyTTLSeconds is the lifetime of a newly-registered agent\n// public key when the operator has not configured agentKeyTTLSeconds.\n// 24 hours. Mirrors how OTPTTLSeconds is defaulted at the helper layer.\nconst DefaultAgentKeyTTLSeconds int64 = 86400\n\n// NewAgentKeyStore opens (or creates) the SQLite database at dbPath.\n// The directory is created if it does not exist.\nfunc NewAgentKeyStore(dbPath string) (*AgentKeyStore, error) {\n\tif dbPath == \"\" {\n\t\tdbPath = filepath.Join(\"data\", \"nhp_server.db\")\n\t}\n\n\tdir := filepath.Dir(dbPath)\n\tif err := os.MkdirAll(dir, 0700); err != nil {\n\t\treturn nil, fmt.Errorf(\"keystore: create directory %s: %w\", dir, err)\n\t}\n\n\tdb, err := sql.Open(\"sqlite\", dbPath+\"?_journal_mode=WAL&_busy_timeout=5000\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"keystore: open database %s: %w\", dbPath, err)\n\t}\n\n\t// Connection pool tuning — SQLite is single-writer; one open conn is\n\t// usually correct. Keep a small idle pool for concurrent read queries.\n\tdb.SetMaxOpenConns(1)\n\tdb.SetMaxIdleConns(1)\n\tdb.SetConnMaxLifetime(0)\n\n\tstore := &AgentKeyStore{db: db}\n\tif err := store.migrate(); err != nil {\n\t\tdb.Close()\n\t\treturn nil, fmt.Errorf(\"keystore: migrate: %w\", err)\n\t}","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/server/keystore.go#L19-L55","documentation":"NewAgentKeyStore creates the parent directory of the SQLite database path with os.MkdirAll(dir, 0700) before opening the DB; if directory creation fails it returns 'keystore: create directory %s: %w'. Typical causes are permission denial on the parent, a non-directory existing at that path, or a read-only filesystem. The wrapped error carries the exact OS reason.","triggerScenarios":"Start (or tests via newTestStore) calls NewAgentKeyStore with a dbPath whose directory cannot be created: EACCES/EPERM (insufficient rights), parent path exists as a regular file (ENOTDIR), read-only filesystem (EROFS), or invalid path characters.","commonSituations":"Running nhp-serverd as a non-root user whose cwd/data dir is not writable; systemd service with a hardened ReadWritePaths lacking the data dir; config points dbPath at /etc or another root-owned location; a file named 'data' already exists where the directory should be; container volume mounted read-only.","solutions":["Create/fix the data directory manually with correct ownership: `mkdir -p <dir> && chown <serveruser> <dir>`.","Check the wrapped errno: ENOTDIR means a file occupies the path — remove/rename it; EACCES means fix permissions; EROFS means remount read-write.","If running under systemd, add the DB directory to ReadWritePaths= (or set StateDirectory=).","Pass an explicit writable dbPath in configuration instead of relying on the relative default data/nhp_server.db with an unwritable working directory.","In containers, mount a writable volume at the configured path."],"exampleFix":"// before: silent reliance on relative default in a read-only cwd\nstore, err := NewAgentKeyStore(\"\")\n// after: configure an absolute, writable path\nstore, err := NewAgentKeyStore(\"/var/lib/nhp-server/nhp_server.db\")\n// systemd unit:\n# StateDirectory=nhp-server  -> /var/lib/nhp-server","handlingStrategy":"try-catch","validationCode":"dir := filepath.Dir(dbPath)\nif fi, err := os.Stat(dir); err == nil && !fi.IsDir() {\n\t// a file blocks the directory path; move it before starting\n}\nif err := syscall.Access(filepath.Dir(dir), syscall.O_RDWR); err != nil {\n\t// parent not writable by this user\n}","typeGuard":"func writableDir(path string) bool {\n\tfi, err := os.Stat(path)\n\treturn err == nil && fi.IsDir() && fi.Mode().Perm()&0200 != 0\n}","tryCatchPattern":"store, err := server.NewAgentKeyStore(dbPath)\nif err != nil {\n\tvar perr *os.PathError\n\tif errors.As(err, &perr) && errors.Is(perr.Err, os.ErrPermission) {\n\t\t// fix ownership / run with proper user or StateDirectory\n\t}\n\tlog.Fatalf(\"keystore init failed: %v\", err)\n}","preventionTips":["Configure an absolute dbPath in a writable, dedicated directory (e.g. /var/lib/nhp-server).","Run the daemon under a service account that owns its data dir.","Under systemd use StateDirectory= / ReadWritePaths=.","Ensure no regular file occupies the intended directory path.","Mount container volumes read-write at the DB location.","Pre-create the directory in deployment scripts with correct ownership."],"tags":["filesystem","mkdir","sqlite","permissions","server"],"backgroundTag":"mkdir-permission-denied","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}