{"record":{"id":"1c97005f34dcd925","repo":"OpenNHP/opennhp","slug":"failed-to-create-etc-directory-v","errorCode":null,"errorMessage":"failed to create etc directory: %v","messagePattern":"failed to create etc directory: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/db/utils.go","lineNumber":68,"sourceCode":"\td = &DataPrivateKeyStore{}\n\t_ = d.fromJson(fileContentByte)\n\n\treturn\n}\n\nfunc (d *DataPrivateKeyStore) Generate(mode ztdolib.DataKeyPairECCMode) (privateKey []byte) {\n\tecdh := core.NewECDH(mode.ToEccType())\n\td.DataPrivateKeyBase64 = ecdh.PrivateKeyBase64()\n\treturn ecdh.PrivateKey()\n}\n\n// Save saves the dataPrivateKeyBase64 to a file, the format of file name is data-<doId>.json\n// Notes: this default way to store data private key is not safe. In the wild environment, need to use a secure way to store data private key.\nfunc (d *DataPrivateKeyStore) Save(doId string) error {\n\t// Make sure the etc directory exists\n\tetcDir := \"etc/ztdo\"\n\tif err := os.MkdirAll(etcDir, 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create etc directory: %v\", err)\n\t}\n\n\tfileName := \"data-key-\" + doId + \".json\"\n\tfullPath := filepath.Join(common.ExeDirPath, etcDir, fileName)\n\tif _, err := os.Stat(fullPath); err == nil {\n\t\treturn fmt.Errorf(\"%v already exists, please delete it first\", fullPath)\n\t}\n\n\tfile, err := os.Create(fullPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create file: %v\", err)\n\t}\n\tdefer file.Close()\n\n\t_, err = file.Write(d.toJson())\n\treturn err\n}\n","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/db/utils.go#L50-L86","documentation":"DataPrivateKeyStore.Save persists the data private key under etc/ztdo and first ensures that directory exists via os.MkdirAll(etcDir, 0755). If directory creation fails - permission denied, path exists as a regular file, or read-only filesystem - Save returns this error and no key file is written.","triggerScenarios":"Calling Save(doId) when: (1) etc/ztdo cannot be created because the process user lacks write permission on <exeDir>; (2) 'etc' already exists as a regular file rather than a directory; (3) the filesystem containing common.ExeDirPath is mounted read-only (e.g. container image layer, read-only volume).","commonSituations":"Running nhp-db as non-root in a container where the exe directory is read-only; systemd service with ProtectSystem=strict without ReadWritePaths for etc/; a stray file named etc blocking MkdirAll.","solutions":["Check the wrapped os error: for 'permission denied', grant the process user write access to <exeDir> (chown/chmod) or run with an account that has it.","Ensure etc/ztdo's parent is a writable directory, not a read-only mount; remount rw or point the daemon at a writable location.","Verify 'etc' under the executable directory is a directory (mv a stray 'etc' file out of the way).","In Docker, mount a volume for etc/ztdo (e.g. -v nhp-db-etc:/app/etc/ztdo) instead of writing into the image layer."],"exampleFix":"// before: docker run with read-only rootfs and no volume\n// docker run --read-only opennhp/nhp-db\n// after: writable volume for the key store\n// docker run --read-only -v nhp-db-etc:/app/etc/ztdo opennhp/nhp-db","handlingStrategy":"validation","validationCode":"dir := filepath.Join(common.ExeDirPath, \"etc\")\nif st, err := os.Stat(dir); err == nil && !st.IsDir() {\n\treturn fmt.Errorf(\"%s exists and is not a directory\", dir)\n}\nif err := syscall.Access(dir, syscall.W_OK); err != nil && !os.IsNotExist(err) {\n\treturn fmt.Errorf(\"no write permission on %s: %w\", dir, err)\n}","typeGuard":"func canWriteDir(path string) bool {\n\tst, err := os.Stat(path)\n\treturn err == nil && st.IsDir() && unix.Access(path, unix.W_OK) == nil\n}","tryCatchPattern":"if err := store.Save(doId); err != nil {\n\tif errors.Is(errors.Unwrap(err), fs.ErrPermission) {\n\t\tlog.Fatalf(\"cannot write key dir: fix permissions on %s: %v\", common.ExeDirPath, err)\n\t}\n\treturn err\n}","preventionTips":["Provision etc/ztdo with correct ownership at install time instead of relying on MkdirAll at runtime.","Do not run daemons from read-only directories; keep writable state on a dedicated volume.","In systemd units, add ReadWritePaths=<exeDir>/etc for hardening options like ProtectSystem.","Check that no stray file named 'etc' exists under the executable directory."],"tags":["filesystem","directory","permissions"],"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"}