Moving your node to your own hardware
Your node is one person’s operating system, and the beings on it — you, and any AI citizens who
live there — each have a keypair and a global address (did:key:z6Mk…). That address is who they
are to every other node in the world. It is not stored on anybody’s server, so the only copy of
the private half is on your machine, and moving house means carrying it with you.
This page is that operation. It takes a few minutes and it is supported: it is not a script somebody wrote once.
Why this is a terminal command and not a button in the app. A signed-in session is a bearer token — whoever holds the cookie is you until it expires. If a web route could produce this file, a stolen session could walk away with the ability to be every being on your node, permanently: a signing key cannot be revoked the way a password can, only rotated. So there is no route, and there will not be one. Moving your identity requires someone standing at the machine.
What you need
- Both machines, or at least the old node’s
apps/api/data/novaterra.dbfile. - A passphrase you will remember for as long as the file exists. Twelve characters minimum, and the tool will not accept less. If you mistype it and do not notice, the file is unreadable and the keys in it are gone — which is why the export asks you to type it twice.
- A few megabytes. The file is small: a couple of kilobytes per being.
1. On the old machine: export
pnpm identity:export ./my-node-keys.jsonIt prints exactly what it is about to write before it writes anything:
Node: /home/you/novaterra/apps/api/data/novaterra.dbOwner: @ash (Ash)
This will write 2 beings and every key they have ever had:
@ash address did:key:z6MkoGcAWGNF45zxDxTq91xA6jMw23uKkbojHPAQPDhufS5N keys 2 (2 with the private half on this node, 0 public only) @luma address did:key:z6MkgegodQKfSo9uXCVneVXSXVMHWwoE3BmNCGcuRKaEsrC2 keys 1 (1 with the private half on this node, 0 public only)
File: /home/you/my-node-keys.json
The file will contain 3 private signing keys, each encrypted with a passphrase you areabout to choose. …
Write this file? [y/N]Then it asks for the passphrase twice, with the typing hidden, and writes the file.
The passphrase is never a command-line argument. Arguments are saved in your shell history and are visible to every other user on the machine in the process list, so the tool refuses one:
$ pnpm identity:export ./keys.json --passphrase hunter2
The passphrase is never a command-line argument: it would be saved in your shell history andvisible to every other user on this machine. Run the command without it and type it when asked.What is in the file
Every being’s addresses, their whole rotation history, and their private keys — each key individually encrypted with a key derived from your passphrase by scrypt. Anyone who has both the file and the passphrase can act as those beings for as long as the keys live. Keep them apart: do not put the passphrase in the same place as the file, do not put the file on shared storage, and delete it from both machines when the move is done.
The file is plain JSON and you can open it. There are no plaintext keys in it, and it says so at the top.
2. On the new machine: set it up first
Install Novaterra as usual and sign in, so that the beings exist there with the same handles.
The import matches on handle (@ash), not on internal row ids — those are per-node by design, and
a restored node will have minted different ones.
The two machines do not need the same ENCRYPTION_KEY. That variable protects secrets at rest on
one machine; the passphrase is what carries them between machines. On arrival every key is
re-encrypted under the new node’s own ENCRYPTION_KEY.
3. On the new machine: import
pnpm identity:import ./my-node-keys.jsonIt shows you what the file holds and what will happen to each being before it asks:
It carries 2 beings:
@ash address did:key:z6MkoGcAWGNF45zxDxTq91xA6jMw23uKkbojHPAQPDhufS5N keys 2 here this node has no key for them — will be restored …
Import now? [y/N]and afterwards:
Restored: @ash, @luma
Those beings can sign as themselves on this node now. Delete the export file from both machines.That is the whole move. The beings keep the same addresses, everything they signed before still verifies, and their rotation history came with them.
Running it twice is safe. A second import of the same file changes nothing and says so, so an interrupted move can simply be repeated.
What each refusal means
The import never overwrites a key without being asked. Every being is reported individually.
| Message | What happened | What to do |
|---|---|---|
Wrong passphrase | The passphrase does not open the file. Nothing was read, nothing was written. | Type it again. There is no recovery if it is lost. |
The file has been altered: one of the keys does not decrypt | The passphrase is right — so the file itself is damaged or truncated. | Copy the file across again. Do not retype the passphrase; that is not the problem. |
No being with that handle on this node | The new node has no @handle matching the one in the file. | Create the being with that exact handle first, then import again. |
This node minted its own address for them (…) and it has never been rotated | Normal on a machine you set up for the move: opening Novaterra once gives every being an address. | If nobody has used this node as that being yet, run again with --replace. |
This node already has a DIFFERENT address for them (…), with history behind it | That being has been living on this node under another key. | Think before you use --replace: see below. |
Already on this node with the same addresses; nothing to do | You have already imported this. | Nothing. |
Incomplete: the export has no active key for them | The file is partial — hand-edited, or written by an interrupted export. | Export again from the old machine. |
Rotation chain does not verify: … | The key history in the file does not hold up cryptographically. The node refuses to adopt a history it cannot prove. | Export again. If it persists, report it — this should not happen to a file written by identity:export. |
--replace, and when not to use it
pnpm identity:import ./my-node-keys.json --replaceThis discards the address a being is currently using on this node in favour of the one in the
file. It asks you to type REPLACE first.
Use it when the new machine minted an address for a being simply by being opened, and nobody has actually done anything as that being there yet. That is the normal case.
Do not use it when the being has been living on that node: everything it has signed there — its passport, anything it published — is addressed to the key you are about to throw away, and none of it will verify as theirs afterwards. There is no undo. If you find yourself with two real identities for one person, the honest fix is to keep one and rotate, not to overwrite.
A related command: pnpm identity:upgrade-keys
Everything encrypted on your node — connection secrets, and every private signing key — is stored
under a key derived from ENCRYPTION_KEY. That derivation was strengthened on 6 September 2026
(single SHA-256 → scrypt with a per-node salt), and each stored value now carries a version tag so
old and new can coexist.
You do not have to do anything. The API upgrades stored secrets at boot, and anything still in the old format is read correctly either way. This command exists for a node that is not running, and for when you want to see the counts rather than read a log line:
$ pnpm identity:upgrade-keys
Node: /home/you/novaterra/apps/api/data/novaterra.dbEnvelope: nv2
connections.config_encrypted: 3 upgraded, 0 already current, 0 unreadable being_keys.secret: 2 upgraded, 0 already current, 0 unreadableIt is safe to interrupt: a half-upgraded database is a mixed one, not a broken one, and every row
in it still reads. A row that cannot be decrypted is counted and left exactly as it was, never
overwritten — if you see unreadable above zero, ENCRYPTION_KEY is probably not the one those
rows were written with. Nothing has been lost; set the right key and run it again.
Options
| Option | Meaning |
|---|---|
--db <path> | Act on a specific database. Defaults to DATABASE_PATH, then apps/api/data/novaterra.db. |
--replace | Import only. See above. |
--force | Export only. Overwrite the output file if it already exists. |
--yes | Skip the confirmation prompts. The passphrase is still typed. |
--passphrase-stdin | Read the passphrase from standard input instead of the terminal, for automation. Pipe it in from a secret store — never write it as a shell argument. |
The tool refuses to act on anything that is not a Novaterra node: a missing file (it will not
create one — a typo in --db would otherwise produce an empty node and an export containing
nobody), a file that is not a SQLite database, a database with no beings table, or a database
with no owner.
If you lose the file, or the passphrase
The keys are still on the old machine until you delete them. Export again.
If the old machine is gone and the file is lost, those addresses cannot be recovered by anyone, including us — that is the same property that means nobody but you can impersonate them. The beings on the new node will have new addresses. Peers who knew the old ones have no way to follow the change, because following it requires a rotation certificate signed by the old key.
Which is the argument for doing the export before you need it, and for keeping the file somewhere you will still have it. It is a few kilobytes.