On this page

Moving the vault owner

Moving the vault owner

stado credentials vault move --to TARGET --owner-uid UID [--apply] [--json] moves the placement profile carrying Skarbiec and its required vault state. When the destination's keyring holds no secret key for the requested owner, --apply makes it there first with skarbiec owner key mint UID (the parameters Skarbiec's init uses; a UID that names a key without its secret half is refused there, naming it). The move then registers its public key with Skarbiec, rotates the owner, relocates the profile, declares the destination vault and withdraws the former host's vault declaration. It then requires the destination's keys check to report readable and compares the complete live-item identities with the source inventory.

Last, the owner's maintenance follows the vault. Skarbiec's recurring work on the vault's owner is skarbiec maintain (it proves the vault decrypts, pulls every bond and pushes the Git ciphertext mirror), and only the owner publishes the mirror. The move makes the destination publish its mirror (skarbiec mirror publish) and, through the destination's own Stado, declares a routine of that host running skarbiec maintain every minute (stado schedule create --host-store, under an id derived from the host, so repeating the move or finishing it changes nothing). A host routine is fired by the host's own worker whatever fleet work the host refuses for its load; a fleet job pinned to a busy owner would wait as long as it stays busy. The receipt reports it as maintenance. A destination without a mirror refuses the step, naming what stands and skarbiec mirror init <remote-url> as the way to make one there; run the same move again afterwards to finish. The former owner stops publishing and its routine is removed, reported as former_mirror (not_unpublished or not_removed with the reason when it could not be done; the vault has already moved, so that is not a refusal). Finishing a move on the host the directory already places the vault on applies the same maintenance to that host.

A plain stado placement move of the profile carrying Skarbiec is refused, placement profile <p> carries skarbiec, the fleet vault, whose owner key does not move with it; move it with stado credentials vault move …, because the ciphertext would land where no key opens it. The vault move runs the placement move itself with --vault-owner-rotated once the owner is rotated (or already is the uid the destination holds). stado placement evacuate --vault-owner-uid UID runs this move as its first step, after its check (this command without --apply) found the placement check passing; a destination without UID's secret key is not a refusal there, because the applied move makes it. Without --vault-owner-uid the evacuation refuses the profile, naming the option.

Without --apply, it only reads and reports. rotation_required says whether the source vault still needs an owner rotation. If an earlier attempt rotated the owner but did not move placement, repeat the same invocation: an owner that already matches is retained, not rotated again. A placement refusal in that state explicitly says no owner rotation was performed and names the starting host and owner. It directs inspection of the placement profile rather than assuming that a failed child command proves the current placement. It does not attempt to rotate the vault to that same owner.

Before any owner mutation, --apply checks the exact destination unit-file paths declared by the profile. A missing file returns not_found, names its path and directs the operator to stado placement standby PROFILE --host TARGET --reason REASON. A profile entry alone is not evidence that its units were staged. Placement repeats its own preflight inside the transaction.

Both with and without --apply, the command first runs stado placement move <profile's services> --to-host TARGET --check, the move's whole preflight against the real hosts, claiming nothing, and reports its answer as placement_check ({"refused": "<cause>"} when it would be refused; in text, the placement move of <profile> to <target> passes its preflight or … would be refused: <cause>). With --apply, a refused check stops the command before the owner is registered or rotated: the placement move of <profile> to <target> would be refused, so nothing was registered or rotated: <cause>, so a source that is unhealthy or a destination that is full no longer costs a rotation and a rotation back.

stado placement standby "$PROFILE" --host "$TARGET" --reason "$REASON" --json
stado credentials vault move --to "$TARGET" --owner-uid "$OWNER_UID" --json
stado credentials vault move --to "$TARGET" --owner-uid "$OWNER_UID" --apply --json
stado credentials vault show --json

A placement failure after a new rotation attempts to restore the previous owner. The previous public key is exported through skarbiec recipient export, so Skarbiec resolves its crypto executable rather than requiring gpg on the source SSH shell's search path. A failed restoration remains a failure with its cause. Once placement commits, a later declaration or verification failure reports what moved and which vault declarations remain; it does not claim the vault was moved back.

A move whose placement committed but whose declarations did not follow (the caller ended, or could not read the committed report) is finished by the same command: --to naming the host the directory already places the vault on does not refuse it as already there. It reads that host's vault owner, refused unless it is --owner-uid (the directory places the fleet vault on <host>, whose vault is owned by <uid>, not <uid>; the move to <host> is done …), and the vault file each host of the profile declares. That host must declare the vault the profile carries or none (<host> declares secrets.skarbiec.vault_file <file>, not <vault>, … which one is the fleet vault cannot be told). With --apply that host declares $HOME/<vault> first, then every other host of the profile withdraws its declaration; a declaration that fails names the host and step, and the command is run again. The report (placed_on, vault, owner_uid, declare, declarations with each host's declared file and action keep, declare or withdraw, key_doctor, live_items, applied) also carries that host's keys check, which must be readable. placement move --json writes its progress to stderr, so its report is the only thing on stdout; a report that still cannot be read is quoted with the start of stdout and the end of stderr and names stado placement history and stado placement holds <host>, where whether the move committed is read.

Stado Desktop exposes the same operation in Hosts → Credentials → Available operations… → Move the fleet vault and its owner to another host. Leave Move it blank for the read-only report; select it to apply. The operation sheet retains the complete result or refusal from the selected Stado API.

Real relocation qualification

tests/credentials_move/vault_move.mjs runs the genuine Stado and Skarbiec executables against a prepared, isolated two-home deployment over real host channels. It must not point at an operator's fleet. Put STADO_BIN, SKARBIEC_BIN and VAULT_MOVE_FIXTURE in the selected environment file:

node --env-file=.build/vault-move/journey.env tests/credentials_move/vault_move.mjs

The fixture JSON names source and destination, each with host, home, vault, keyring, owner_uid and unit_file. It supplies the dedicated ssh_key, a seeded sentinel item and sentinel_fields, the known field values whose hashes real decryption must return. Both keyrings hold the other owner's public key, and the source vault registers the destination recipient. The source home contains the isolated Stado configuration and local store. Homes and mutable fixture paths must resolve inside the checkout's ignored .build directory, and real host configuration reads must resolve those homes before the journey writes.

The journey checks missing-key and missing-unit refusals without changing the source vault, creates the already-rotated intermediate state through Skarbiec, checks a failed continuation leaves that owner unchanged, applies the move through Stado, verifies the saved placement and declarations, compares item identities and known decrypted field hashes, and moves back. Failure cleanup attempts to restore the fixture's original placement and owner and retains any restoration error. Reports under .build/credentials-vault-move-*/report.json retain the exact source revision, patch, binary and test digests, commands, exit statuses and actual output. An absent or invalid fixture is blocked, exits nonzero and is not a passing relocation qualification. Reading a plan alone is not evidence of a move.

Source: this website