OpenNHP/opennhp · error
failed to create file
Error message
failed to create file: %v
What it means
After passing the existence check, Save creates the key file with os.Create. If file creation fails - permission denied in the etc/ztdo directory, invalid doId producing an illegal filename, or disk full - this error is returned and no key material is persisted.
Solutions
- Check the wrapped os error: for 'permission denied', fix ownership/permissions of <exeDir>/etc/ztdo so the process user can create files.
- Validate doId before saving - it should be a UUID or safe identifier without path separators or illegal characters.
- Check disk space and inodes (df / df -i) on the volume holding etc/ztdo.
- Pre-create the directory with correct ownership, or run the daemon under an account with write access to its exe directory.
Example fix
// before: unchecked doId can build an invalid path
if err := store.Save(doId); err != nil { return err }
// after: validate doId as a UUID first
if _, err := uuid.Parse(doId); err != nil {
return fmt.Errorf("invalid doId %q: %w", doId, err)
}
if err := store.Save(doId); err != nil { return err } Defensive patterns
Strategy: validation
Validate before calling
if _, err := uuid.Parse(doId); err != nil {
return fmt.Errorf("doId must be a UUID, got %q", doId)
}
if err := syscall.Access(filepath.Join(common.ExeDirPath, "etc/ztdo"), syscall.W_OK); err != nil {
return fmt.Errorf("etc/ztdo not writable: %w", err)
} Type guard
func validDoId(doId string) bool {
_, err := uuid.Parse(doId)
return err == nil
} Try / catch
if err := store.Save(doId); err != nil {
if errors.Is(errors.Unwrap(err), fs.ErrPermission) {
return fmt.Errorf("cannot create key file; check ownership of etc/ztdo: %w", err)
}
return err
} Prevention
- Validate doId (UUID) before using it to build filenames.
- Ensure the daemon user owns or can write to etc/ztdo.
- Watch disk space/inodes on the state volume (df -h / df -i alerts).
- Run Save once per doId as part of a single provisioning step.
When it happens
Trigger: Calling Save(doId) when os.Create(fullPath) fails: (1) the process cannot write to the (just-created or existing) etc/ztdo directory; (2) doId contains characters making the path invalid or too long on the OS; (3) no space / inode exhaustion on the volume.
Common situations: Directory owned by root while the daemon runs unprivileged; doId with slashes from a malformed identifier; full disk on a small container volume during key provisioning.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- failed to create etc directory
- fail to write private key file
- fail to write public key file
- keystore: create directory
- failed to create etc directory
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/167c7c481af8caa2.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/db/utils.go:79
// Save saves the dataPrivateKeyBase64 to a file, the format of file name is data-<doId>.json
// 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.
func (d *DataPrivateKeyStore) Save(doId string) error {
// Make sure the etc directory exists
etcDir := "etc/ztdo"
if err := os.MkdirAll(etcDir, 0755); err != nil {
return fmt.Errorf("failed to create etc directory: %v", err)
}
fileName := "data-key-" + doId + ".json"
fullPath := filepath.Join(common.ExeDirPath, etcDir, fileName)
if _, err := os.Stat(fullPath); err == nil {
return fmt.Errorf("%v already exists, please delete it first", fullPath)
}
file, err := os.Create(fullPath)
if err != nil {
return fmt.Errorf("failed to create file: %v", err)
}
defer file.Close()
_, err = file.Write(d.toJson())
return err
}
func (d *DataPrivateKeyStore) Delete(doId string) error {
etcDir := "etc/ztdo"
fileName := "data-key-" + doId + ".json"
fullPath := filepath.Join(common.ExeDirPath, etcDir, fileName)
// delete the file
err := os.Remove(fullPath)
if err != nil {
return fmt.Errorf("failed to delete file: %v", err)
}
return nilView on GitHub (pinned to 6e04ca5ff0)