Add Bitcoin Pulse operations runbook
This commit is contained in:
parent
81fea16376
commit
1bdf25bf06
3 changed files with 271 additions and 16 deletions
250
docs/bitcoin-pulse-operations.md
Normal file
250
docs/bitcoin-pulse-operations.md
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
# Bitcoin Pulse production operations
|
||||
|
||||
This runbook covers routine Bitcoin Pulse deployment and maintenance on
|
||||
Tobserver. The declarative configuration lives in `modules/bitcoin-pulse.nix`;
|
||||
PostgreSQL details are in `bitcoin-pulse-postgresql.md`, and public ingress
|
||||
checks are in `bitcoin-pulse-ingress.md`.
|
||||
|
||||
Run production commands from the Tobserver checkout unless noted otherwise.
|
||||
Never place plaintext credentials in Git, the Nix store, command arguments,
|
||||
shell history, logs, or issue comments.
|
||||
|
||||
## Standard deployment
|
||||
|
||||
Prepare and review every change on a workstation before touching the server:
|
||||
|
||||
```console
|
||||
nix flake check --no-build
|
||||
nix build .#nixosConfigurations.tobserver.config.system.build.toplevel
|
||||
```
|
||||
|
||||
The second command can run only on a compatible Linux builder. It is optional
|
||||
on a workstation without one because the server build below performs the same
|
||||
full configuration build before activation.
|
||||
|
||||
Commit and push the reviewed change. On Tobserver, update the clean checkout and
|
||||
build without changing the running system:
|
||||
|
||||
```console
|
||||
cd ~/tobserver
|
||||
test -z "$(git status --porcelain)" || {
|
||||
echo "Refusing to deploy from a dirty checkout" >&2
|
||||
exit 1
|
||||
}
|
||||
git pull --ff-only
|
||||
git rev-parse HEAD
|
||||
nix flake check --no-build
|
||||
sudo nixos-rebuild build --flake .#tobserver
|
||||
```
|
||||
|
||||
The cleanliness check stops the procedure before pulling when the checkout has
|
||||
local changes. Compare the printed commit with the approved deployment commit.
|
||||
Review build failures before continuing. A successful `build` creates the
|
||||
ignored `result` symlink but does not restart any service.
|
||||
|
||||
Activate the already-reviewed configuration:
|
||||
|
||||
```console
|
||||
sudo nixos-rebuild switch --flake .#tobserver
|
||||
```
|
||||
|
||||
Do not use a live full-host NixOS generation rollback as a Bitcoin Pulse test or
|
||||
routine recovery action. Tobserver runs unrelated production services that a
|
||||
full generation switch can also change.
|
||||
|
||||
## Post-deployment verification
|
||||
|
||||
Verify service state, migration completion, loopback-only listeners, and local
|
||||
responses:
|
||||
|
||||
```console
|
||||
sudo systemctl is-active \
|
||||
podman-bitcoin-pulse-postgres.service \
|
||||
bitcoin-pulse.service \
|
||||
nginx.service
|
||||
|
||||
sudo systemctl show bitcoin-pulse-migrate.service \
|
||||
--property=ActiveState \
|
||||
--property=Result \
|
||||
--property=ExecMainStatus
|
||||
|
||||
sudo ss -ltnp | grep -E ':(3000|5432)\b'
|
||||
|
||||
curl --fail --show-error --silent http://127.0.0.1:3000/health \
|
||||
| jq -e '.status == "OK"'
|
||||
curl --fail --show-error --silent http://127.0.0.1:3000/block-height \
|
||||
| jq -e .
|
||||
curl --fail --show-error --silent http://127.0.0.1:3000/difficulty-adjustment \
|
||||
| jq -e .
|
||||
curl --fail --show-error --silent http://127.0.0.1:3000/price \
|
||||
| jq -e .
|
||||
```
|
||||
|
||||
All three long-running services must be active. The migration unit is a
|
||||
completed one-shot, so `ActiveState=inactive`, `Result=success`, and
|
||||
`ExecMainStatus=0` are expected. PostgreSQL and the backend must listen only on
|
||||
`127.0.0.1:5432` and `127.0.0.1:3000` respectively.
|
||||
|
||||
From a machine outside Tobserver, run the public checks in
|
||||
`bitcoin-pulse-ingress.md`. Inspect relevant logs without copying credentials:
|
||||
|
||||
```console
|
||||
sudo journalctl \
|
||||
-u podman-bitcoin-pulse-postgres.service \
|
||||
-u bitcoin-pulse-migrate.service \
|
||||
-u bitcoin-pulse.service \
|
||||
-u nginx.service \
|
||||
--since "-15 minutes" \
|
||||
--no-pager
|
||||
```
|
||||
|
||||
## Updating the Bitcoin Pulse backend
|
||||
|
||||
Update only the pinned backend input from a workstation:
|
||||
|
||||
```console
|
||||
cd ~/Code/Tobserver
|
||||
nix flake update bitcoin-pulse
|
||||
git diff -- flake.lock
|
||||
nix flake check --no-build
|
||||
```
|
||||
|
||||
Confirm that `flake.lock` points to the intended reviewed BitcoinPulseAPI
|
||||
revision. Commit and push the lock-file update, follow the standard deployment,
|
||||
and perform all post-deployment checks. The switch runs the packaged version's
|
||||
pending migrations before starting that backend version.
|
||||
|
||||
Every database migration must remain compatible with the previously deployed
|
||||
application while it is still a possible recovery version. Do not run
|
||||
`bitcoin-pulse-migrate rollback` automatically.
|
||||
|
||||
## Backups
|
||||
|
||||
The daily logical backup, retention policy, credentials, and initial
|
||||
verification procedure are documented in `bitcoin-pulse-postgresql.md`.
|
||||
|
||||
Before a database image change or other maintenance with data risk, create and
|
||||
verify a fresh snapshot:
|
||||
|
||||
```console
|
||||
sudo systemctl start restic-backups-bitcoin-pulse.service
|
||||
sudo systemctl show restic-backups-bitcoin-pulse.service \
|
||||
--property=Result \
|
||||
--property=ExecMainStatus
|
||||
sudo restic-bitcoin-pulse snapshots --latest 1
|
||||
sudo restic-bitcoin-pulse ls latest | grep bitcoin-pulse.dump
|
||||
sudo find /var/backup/bitcoin-pulse -maxdepth 1 -type f -ls
|
||||
```
|
||||
|
||||
Expect `Result=success`, `ExecMainStatus=0`, a snapshot containing
|
||||
`bitcoin-pulse.dump`, and no local staging file after completion. Alerting and
|
||||
routine restore tests are separate backlog work.
|
||||
|
||||
## PostgreSQL image updates within the current major version
|
||||
|
||||
Automatic image updates are disabled. For a PostgreSQL 18 patch or minor image
|
||||
update:
|
||||
|
||||
1. Read the official PostgreSQL image release notes and confirm the data
|
||||
directory remains compatible.
|
||||
2. Create and verify a fresh Bitcoin Pulse backup.
|
||||
3. Test the candidate image with the backend package and migrations outside
|
||||
production where practical.
|
||||
4. Verify that the candidate digest identifies the intended PostgreSQL 18
|
||||
Bookworm patch release, then replace the immutable digest in
|
||||
`modules/bitcoin-pulse.nix`.
|
||||
5. Update the adjacent exact-version and tested-date comment and the declared
|
||||
version in `bitcoin-pulse-postgresql.md` in the same change.
|
||||
6. Run `nixpkgs-fmt --check modules/bitcoin-pulse.nix` and
|
||||
`nix flake check --no-build`.
|
||||
7. Review, commit, push, and follow the standard deployment procedure.
|
||||
8. Repeat all post-deployment and backup checks.
|
||||
|
||||
Never enable Podman automatic updates or deploy a floating image tag.
|
||||
|
||||
## PostgreSQL major upgrades
|
||||
|
||||
A major PostgreSQL upgrade is a separate maintenance project, not a routine
|
||||
image-digest change. A new major version must never start against the existing
|
||||
`bitcoin-pulse-db` volume because PostgreSQL data directories are not
|
||||
major-version compatible.
|
||||
|
||||
Before approving a major upgrade, prepare a version-specific plan that:
|
||||
|
||||
1. Defines a maintenance window and stops the Bitcoin Pulse backend from
|
||||
accepting writes during the final export.
|
||||
2. Creates and verifies a fresh logical backup with the old PostgreSQL version.
|
||||
3. Provisions a new, separately named Podman volume for the new major version.
|
||||
4. Initializes the administrator, migration, and application roles in the new
|
||||
cluster without changing or deleting the old volume.
|
||||
5. Restores the logical dump into the new cluster before normal backend startup.
|
||||
6. Runs Migratus, starts the pinned backend, and repeats database, endpoint,
|
||||
collector, ingress, and backup verification.
|
||||
7. Keeps the old volume untouched until the new cluster has been explicitly
|
||||
accepted and a new backup has completed.
|
||||
8. Documents a version-specific recovery path before removing any old data.
|
||||
|
||||
Do not improvise this procedure directly on production. The exact export,
|
||||
restore, and compatibility steps depend on the source and target PostgreSQL
|
||||
versions and require a separately reviewed implementation change.
|
||||
|
||||
## Secret rotation
|
||||
|
||||
PostgreSQL role rotation is documented in the credential-rotation section of
|
||||
`bitcoin-pulse-postgresql.md`. Rotate one role at a time and synchronize the
|
||||
database role password with its SOPS value during a maintenance window.
|
||||
|
||||
The shared `backblaze/tobcloud-backup-env` credential affects both Bitcoin Pulse
|
||||
and Nextcloud backups. Coordinate its rotation across both services, deploy the
|
||||
new SOPS value once, and verify read, write, and retention access by running
|
||||
both backup jobs during a maintenance window:
|
||||
|
||||
```console
|
||||
sudo systemctl start restic-backups-bitcoin-pulse.service
|
||||
sudo systemctl start restic-backups-nextcloud.service
|
||||
|
||||
sudo systemctl show \
|
||||
restic-backups-bitcoin-pulse.service \
|
||||
restic-backups-nextcloud.service \
|
||||
--property=Id \
|
||||
--property=Result \
|
||||
--property=ExecMainStatus
|
||||
|
||||
sudo restic-bitcoin-pulse snapshots --latest 1
|
||||
sudo restic-nextcloud snapshots --latest 1
|
||||
```
|
||||
|
||||
Both units must report `Result=success` and `ExecMainStatus=0`, and both
|
||||
repositories must contain a fresh snapshot.
|
||||
|
||||
A Restic repository password is independent of the Backblaze application key.
|
||||
Retain each repository password in the approved password manager; losing one
|
||||
makes that repository unreadable.
|
||||
|
||||
## First-response diagnostics
|
||||
|
||||
For an unhealthy deployment, gather state before restarting services:
|
||||
|
||||
```console
|
||||
sudo systemctl status \
|
||||
podman-bitcoin-pulse-postgres.service \
|
||||
bitcoin-pulse-migrate.service \
|
||||
bitcoin-pulse.service \
|
||||
nginx.service \
|
||||
--no-pager
|
||||
|
||||
sudo journalctl \
|
||||
-u podman-bitcoin-pulse-postgres.service \
|
||||
-u bitcoin-pulse-migrate.service \
|
||||
-u bitcoin-pulse.service \
|
||||
-u nginx.service \
|
||||
-b \
|
||||
-n 300 \
|
||||
--no-pager
|
||||
```
|
||||
|
||||
A migration failure intentionally prevents backend startup. Correct the
|
||||
database readiness, migration, or credential error and then restart
|
||||
`bitcoin-pulse.service`; its dependency starts the idempotent migration unit
|
||||
again. Do not delete the PostgreSQL volume, bypass migrations, expose ports 3000
|
||||
or 5432 publicly, or paste unredacted logs into an issue.
|
||||
Loading…
Add table
Add a link
Reference in a new issue