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