OpenNHP/opennhp · error
already exists, please delete it first
Error message
%v already exists, please delete it first
What it means
Save refuses to overwrite an existing data private key file. Before creating data-key-<doId>.json it runs os.Stat; if the file already exists it returns this error instructing the caller to delete it first. This is an intentional safety guard against silently replacing a ZTDO data private key, which would make previously wrapped data undecryptable.
Solutions
- If the existing key is still valid, load it with NewDataPrivateKeyStoreWith(doId) instead of generating a new one.
- If the key must be rotated, explicitly call Delete(doId) (or remove the file) first, understanding that data wrapped with the old key becomes undecryptable.
- Before saving, check existence with os.Stat on <exeDir>/etc/ztdo/data-key-<doId>.json and branch your logic accordingly.
- Fix duplicate doId generation if the same identifier is being reused across runs.
Example fix
// before: blind Save fails on second run
if err := store.Save(doId); err != nil { return err }
// after: only save when no key exists yet
path := filepath.Join(common.ExeDirPath, "etc/ztdo", "data-key-"+doId+".json")
if _, err := os.Stat(path); os.IsNotExist(err) {
if err := store.Save(doId); err != nil { return err }
} Defensive patterns
Strategy: validation
Validate before calling
path := filepath.Join(common.ExeDirPath, "etc/ztdo", "data-key-"+doId+".json")
exists := false
if _, err := os.Stat(path); err == nil { exists = true }
if exists {
// load existing key instead of saving a new one
} Type guard
func keyAlreadySaved(doId string) bool {
_, err := os.Stat(filepath.Join(common.ExeDirPath, "etc/ztdo", "data-key-"+doId+".json"))
return err == nil
} Try / catch
err := store.Save(doId)
if err != nil && strings.Contains(err.Error(), "already exists") {
store, err = db.NewDataPrivateKeyStoreWith(doId) // reuse existing key
} Prevention
- Make bootstrap scripts idempotent: load an existing key before generating a new one.
- Never reuse doIds across provisioning runs; always generate fresh UUIDs.
- If rotation is intended, call Delete(doId) explicitly and acknowledge old wrapped data becomes undecryptable.
- Document that Save is intentionally non-overwriting.
When it happens
Trigger: Calling Save(doId) when data-key-<doId>.json already exists in <exeDir>/etc/ztdo: (1) re-generating a key for a doId that already has one; (2) a leftover file from a previous run with the same doId; (3) calling Save twice in one flow.
Common situations: Re-running an init/registration step on an already-provisioned daemon; a doId collision (same UUID reused); operator retries a bootstrap script without cleaning up the first attempt's key file.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- failed to open file
- resource not found
- Error: fail to generating temporary file path
- error reading file
- failed to create etc directory
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/1fd74737f1bc8ef4.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/db/utils.go:74
func (d *DataPrivateKeyStore) Generate(mode ztdolib.DataKeyPairECCMode) (privateKey []byte) {
ecdh := core.NewECDH(mode.ToEccType())
d.DataPrivateKeyBase64 = ecdh.PrivateKeyBase64()
return ecdh.PrivateKey()
}
// 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 fileView on GitHub (pinned to 6e04ca5ff0)