OpenNHP/opennhp · error
keystore: create directory
Error message
keystore: create directory %s: %w
What it means
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.
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.
Example fix
// before: silent reliance on relative default in a read-only cwd
store, err := NewAgentKeyStore("")
// after: configure an absolute, writable path
store, err := NewAgentKeyStore("/var/lib/nhp-server/nhp_server.db")
// systemd unit:
# StateDirectory=nhp-server -> /var/lib/nhp-server Defensive patterns
Strategy: try-catch
Validate before calling
dir := filepath.Dir(dbPath)
if fi, err := os.Stat(dir); err == nil && !fi.IsDir() {
// a file blocks the directory path; move it before starting
}
if err := syscall.Access(filepath.Dir(dir), syscall.O_RDWR); err != nil {
// parent not writable by this user
} Type guard
func writableDir(path string) bool {
fi, err := os.Stat(path)
return err == nil && fi.IsDir() && fi.Mode().Perm()&0200 != 0
} Try / catch
store, err := server.NewAgentKeyStore(dbPath)
if err != nil {
var perr *os.PathError
if errors.As(err, &perr) && errors.Is(perr.Err, os.ErrPermission) {
// fix ownership / run with proper user or StateDirectory
}
log.Fatalf("keystore init failed: %v", err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.
Related errors
- failed to create etc directory
- failed to create file
- fail to create private key directory
- fail to create public key directory
- fail to check resource
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/9ab918351571007d.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/keystore.go:37
type AgentKeyStore struct {
db *sql.DB
}
// DefaultAgentKeyTTLSeconds is the lifetime of a newly-registered agent
// public key when the operator has not configured agentKeyTTLSeconds.
// 24 hours. Mirrors how OTPTTLSeconds is defaulted at the helper layer.
const DefaultAgentKeyTTLSeconds int64 = 86400
// NewAgentKeyStore opens (or creates) the SQLite database at dbPath.
// The directory is created if it does not exist.
func NewAgentKeyStore(dbPath string) (*AgentKeyStore, error) {
if dbPath == "" {
dbPath = filepath.Join("data", "nhp_server.db")
}
dir := filepath.Dir(dbPath)
if err := os.MkdirAll(dir, 0700); err != nil {
return nil, fmt.Errorf("keystore: create directory %s: %w", dir, err)
}
db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
if err != nil {
return nil, fmt.Errorf("keystore: open database %s: %w", dbPath, err)
}
// Connection pool tuning — SQLite is single-writer; one open conn is
// usually correct. Keep a small idle pool for concurrent read queries.
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(0)
store := &AgentKeyStore{db: db}
if err := store.migrate(); err != nil {
db.Close()
return nil, fmt.Errorf("keystore: migrate: %w", err)
}View on GitHub (pinned to 6e04ca5ff0)