OpenNHP/opennhp · error

failed to open file

Error message

failed to open file: %v

What it means

NewDataPrivateKeyStoreWith loads a stored data private key file for a ZTDO from `<exeDir>/etc/ztdo/data-key-<doId>.json`. When os.Open fails - because the key file does not exist, the path is wrong, or the process lacks read permission - the store cannot be built and this wrapped error is returned to the caller (HandleUdpDataKeyWrappingOperations). The wrapped os error names the exact cause (e.g. 'no such file or directory').

Solutions

  1. Check the wrapped os error: if it is 'no such file or directory', verify the file exists at <exeDir>/etc/ztdo/data-key-<doId>.json and that the doId is correct.
  2. Create the key first by constructing a store with NewDataPrivateKeyStore(...), calling Generate(mode), then Save(doId) before loading it.
  3. Verify common.ExeDirPath matches where the keys were originally saved; restart the daemon from the expected directory or move the etc/ztdo directory.
  4. Fix file permissions (chmod/chown) so the process user can read the key file.

Example fix

// before: assumes the key file exists
store, err := db.NewDataPrivateKeyStoreWith(doId)
if err != nil { return err }
// after: generate and save on first use
store, err := db.NewDataPrivateKeyStoreWith(doId)
if err != nil {
	if os.IsNotExist(errors.Unwrap(err)) {
		store = db.NewDataPrivateKeyStore(providerPubB64)
		store.Generate(ztdolib.ECCModeSm2)
		if err := store.Save(doId); err != nil { return err }
	} else {
		return err
	}
}
Defensive patterns

Strategy: try-catch

Validate before calling

path := filepath.Join(common.ExeDirPath, "etc/ztdo", "data-key-"+doId+".json")
if _, err := os.Stat(path); os.IsNotExist(err) {
	// key not yet created; generate and save first
}

Type guard

func keyFileExists(doId string) bool {
	p := filepath.Join(common.ExeDirPath, "etc/ztdo", "data-key-"+doId+".json")
	st, err := os.Stat(p)
	return err == nil && !st.IsDir()
}

Try / catch

store, err := db.NewDataPrivateKeyStoreWith(doId)
if err != nil {
	if errors.Is(err, fs.ErrNotExist) {
		// fall back to generating a new key
	} else {
		return fmt.Errorf("loading data key for %s: %w", doId, err)
	}
}

Prevention

When it happens

Trigger: Calling NewDataPrivateKeyStoreWith(doId) when: (1) the key file was never created via Save() for that doId; (2) the file was deleted or the doId string is mistyped so the filename data-key-<doId>.json does not match; (3) the daemon's working/executable directory changed so common.ExeDirPath-based relative path etc/ztdo no longer resolves; (4) file permissions deny read access.

Common situations: Fresh deployment where keys were generated on another machine; running nhp-db from a different cwd or container path than when keys were saved; UUID/doId mismatch between caller and stored file; volume not mounted in Docker.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/a329c80526655b82. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/db/utils.go:42

// NewDataPrivateKeyStore create a new DataPrivateKeyStore
func NewDataPrivateKeyStore(providerPublicKeyBase64 string) *DataPrivateKeyStore {
	return &DataPrivateKeyStore{
		ProviderPublicKeyBase64: providerPublicKeyBase64,
	}
}

// NewDataPrivateKeyStoreWith create a new DataPrivateKeyStore with doId
func NewDataPrivateKeyStoreWith(doId string) (d *DataPrivateKeyStore, err error) {
	etcDir := "etc/ztdo"
	fileName := "data-key-" + doId + ".json"

	fullPath := filepath.Join(common.ExeDirPath, etcDir, fileName)

	// open and read all the content in file
	file, err := os.Open(fullPath)
	if err != nil {
		return nil, fmt.Errorf("failed to open file: %v", err)
	}

	fileContentByte, err := io.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("error reading file: %v", err)
	}

	d = &DataPrivateKeyStore{}
	_ = d.fromJson(fileContentByte)

	return
}

func (d *DataPrivateKeyStore) Generate(mode ztdolib.DataKeyPairECCMode) (privateKey []byte) {
	ecdh := core.NewECDH(mode.ToEccType())
	d.DataPrivateKeyBase64 = ecdh.PrivateKeyBase64()
	return ecdh.PrivateKey()
}

View on GitHub (pinned to 6e04ca5ff0)