rendered from docs/zone-handoff.md e9f6b9d42c0a

Handing a zone to a new host

An account here is a did:web name in a zone this deployment serves (the names an agent has). Its DID document is served by whoever answers HTTPS for that name. Moving the deployment keeps every DID, so apps keep their at:// URIs, as long as two things move together: the zone’s delegation, and the data volume holding the signing keys.

This page moves a whole deployment. Moving one account to another PDS is evacuation.

What moves, and where it lives

Piece Lives Moves by
Accounts, records, ledger, signing keys The log and pds.checkpoint on the data volume The volume
Blobs blobs/ on the data volume The volume
OAuth grants, spent proofs, e-stop, policy set, firehose sequence pds.grants, pds.proofs, pds.estop, pds.policy, pds.sequence on the data volume The volume
The server’s own account and key The log The volume
ACME account key, certificates tls/ on the data volume The volume
The hosted zone Route53, terraform-aws-didbot-pds/route53.tf A new zone, delegated again from the parent
The delegation The parent zone. For pds.did.bot, infra/site/pds.auto.tfvars An apply of the parent
Rendered config infra/pds variables The same variables in the new stack
The operator’s claim and policy records The operator’s own repository Nothing. They name the server’s DID, which is the zone and does not change

Rules

A new instance in the same AWS account

The data volume outlives the instance (terraform-aws-didbot-pds/ec2.tf). Any change that replaces the instance, such as a new ami_id or a change to its user data, stops the old instance, detaches the volume and attaches it to the new one. The boot script mounts the volume by its id and does not format a volume that holds a filesystem. The Elastic IP moves with the association, so DNS does not change.

A lost volume, restored from backup

Follow Restoring, Mark step included: the backup is up to a day old, and relays hold what it lost. Restoring through Terraform, so infra/pds manages the restored volume, is an open item in evacuation.

A new host in another AWS account

The source and destination are two infra/pds stacks with the same root_zone and nested_zones, in two accounts.

  1. Announce the window. Every name in the zone is offline from step 2 until step 9.
  2. Stop the source. systemctl disable --now didbot-pds, then confirm docker ps --filter name=didbot-pds lists nothing, and sync. Stop the instance too, so nothing restarts the unit.
  3. Snapshot. aws ec2 create-snapshot --volume-id <data volume> and wait for completed. The source stopped cleanly, so this snapshot holds every write.
  4. Share. The snapshot is encrypted under the source stack’s aws_kms_key.ebs, whose policy grants the source account and its backup role. The destination account needs decrypt on that key to copy the snapshot. That grant is the first open item in evacuation. With it, aws ec2 modify-snapshot-attribute --create-volume-permission shares the snapshot with the destination account.
  5. Destination key and zone. In the destination, run Bring-up steps 1 to 3 with -target=module.pds.aws_kms_key.ebs added to step 2. Set operator_did to the operator who will answer for the deployment.
  6. Copy and import the volume. aws ec2 copy-snapshot re-encrypts the shared snapshot under the destination’s aws_kms_key.ebs. Create a volume from the copy in the instance subnet’s availability zone, with the size and type data_volume_size_gb names, then tofu -chdir=infra/pds import module.pds.aws_ebs_volume.data <volume id>. A volume whose key, size, type or zone differ from the stack is a replacement, and prevent_destroy stops that plan.
  7. Delegate. Point the parent at the destination zone’s four name servers: Bring-up steps 4 and 5. Leave the source zone in place until the parent’s NS TTL has passed.
  8. Start. tofu -chdir=infra/pds apply. The instance mounts the imported volume and chowns /data/pds. The server serves the certificate in tls/ while it is valid and the run’s acme_environment matches the one it was issued under. It writes the CAA record for the ACME account in tls/ into the new zone.
  9. Verify. Run Verifying a restore. Leave out the Mark step: nothing was lost, and pds.sequence carries the firehose position.
  10. Claim, if the operator changed. The server polls the configured operator’s repository for its claim and its policies. Until didbot operate <root_zone> <operator_did> runs, it serves reads and refuses writes (the server lifecycle). The new operator publishes their policy records first.
  11. Announce. didbot login, then didbot announce (Announcing to relays). A relay that stopped dialing during the window subscribes again from its cursor.
  12. Retire the source. Keep the source volume, key and zone until the destination has its own recovery points. Deleting the source aws_kms_key.ebs makes the source’s recovery points unreadable.

Sessions across a handoff

Session After the handoff
OAuth grants Kept. pds.grants moves with the volume, and the issuer is the zone’s own endpoint, which does not change. Clients refresh as before
Account tokens from bot.did.createSession Kept, until their own expiry. They are in the log
Sign-ins in flight Lost. They are in memory. The app starts the sign-in again
The operator’s dashboard session Lost. didbot login again
Agent hosts Unchanged. didbot-agentd reaches the server by its zone name

After a restore from backup instead of a stopped source, grants issued after the recovery point are gone. Each app holding one is refused at its next refresh and authorizes again.