Fetches, verifies and places the published artifacts a Mina node needs before it can start: archive database dumps, precomputed blocks and runtime configuration files.
mina-provision archive --network mainnet --pg-uri postgres://... # a database, from a published dump
mina-provision blocks --network mainnet --range 50000-51000 # precomputed blocks, onto disk
mina-provision daemon-config --network mainnet --out /var/lib/coda # the config a daemon auto-loads
mina-provision genesis-config --network mainnet # the fork config a chain starts from# Debian and Ubuntu, from the signed apt repository
sudo install -d -m 0755 /etc/apt/keyrings
sudo wget -qO /etc/apt/keyrings/minaprotocol.gpg \
https://stable.apt.packages.minaprotocol.com/repo-signing-key.gpg
gpg --show-keys /etc/apt/keyrings/minaprotocol.gpg # compare the fingerprint, see below
echo "deb [signed-by=/etc/apt/keyrings/minaprotocol.gpg] https://stable.apt.packages.minaprotocol.com $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/mina.list
sudo apt-get update && sudo apt-get install mina-provision
# or a single .deb from a release, see "Checking a release download" below
sudo dpkg -i mina-provision_<version>_amd64.deb
# or a container
mkdir -p out
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD/out:/out" \
ghcr.io/minaprotocol/mina-provision daemon-config --network mainnet --out /outThe repository key must have this fingerprint. Do not continue if
gpg --show-keys shows a different one:
386E 9DAC 3787 26A4 8ED5 CE56 ADB3 0D9A CE02 F414
signed-by makes apt trust the key for this one source only. A key in
/etc/apt/trusted.gpg.d/ is trusted for every configured source: an index
signed with it would be accepted from any of them, also the Debian or Ubuntu
ones.
arm64 packages exist only for bookworm and noble. The bullseye, focal and
jammy distributions of the repository do not declare arm64, so on an arm64
host with one of them apt-get install fails with "Unable to locate package".
Use the arm64 binary from a release, or the container, there.
The container runs as the unprivileged user provision, uid and gid 10001,
in the working directory /work. Without --user, files written to a mounted
directory belong to uid 10001 on the host, and the directory must be
writable by that uid. With --user "$(id -u):$(id -g)" they belong to you.
Each GitHub Release has a SHA256SUMS file and a build provenance attestation
for every file it has. The attestation is a signed statement that the file was
built by this repository's Release workflow, from the tagged commit.
sha256sum -c --ignore-missing SHA256SUMS
gh attestation verify mina-provision_<version>_amd64.deb -R MinaProtocol/mina-provisionpostgresql-client is a recommendation, not a dependency. Only archive
shells out to psql. The published dumps use \restrict, which needs psql
17.6 or later, or 16.10, 15.14, 14.19 or 13.22 on the older branches. archive
checks this before it loads a dump. The container image has a psql that
qualifies.
Fetches an archive dump, extracts it, applies the recommended PostgreSQL tuning, and loads the SQL.
| Flag | Meaning |
|---|---|
--pg-uri |
the server to restore into; required unless --skip-pg. See Where the dump goes |
--date |
dump date, YYYY-MM-DD; defaults to today, UTC. A date after today (UTC) is refused |
--hour |
dump hour, HHMM, from 0000 to 2359; default 0000. Dumps are produced hourly |
--work-dir |
where the download and the extracted SQL are written; created if missing. Default: the current directory |
--skip-pg |
download and extract only |
--if-present |
what to do when the database already holds an archive: import, skip, fail |
--max-extract-bytes |
the most bytes the extracted dump may hold; extraction stops with an error above it. Default: 200 GiB |
Only the .sql files in the dump are extracted. Other entries are skipped.
ALTER SYSTEM writes to postgresql.auto.conf, so PostgreSQL must be
restarted for the tuning to take effect.
--pg-uri must be a postgres:// or postgresql:// URI. A key=value
connection string (host=… password=…) is refused. archive removes the
password from the URI, from the user info (user:pw@) or from a password
query parameter, and gives it to psql in PGPASSWORD. The password is
therefore not in the arguments of a psql process, which any local user can
read, and not in the log, also with -v.
The password is still in the arguments of mina-provision itself. To keep it
out of them too, leave it out of --pg-uri and set PGPASSWORD or use
~/.pgpass: psql reads both when the URI has no password.
The published dumps are made with pg_dump --create. They start with
CREATE DATABASE archive and \connect archive, so they restore into the
database archive whatever database --pg-uri names. --pg-uri is then only
the way to reach the server: postgres://user:pw@host:5432/postgres and
…/archive both work, also on a new server where archive does not exist yet.
A --pg-uri that names another database gets a warning.
The provider's database setting says which database a dump creates. It is
archive for the built-in providers. Before a dump is loaded, its header is
checked against this setting, and a dump that creates a different database is
refused. For a mirror of dumps made without --create, leave database out:
those dumps restore into the database --pg-uri names.
The load stops at the first SQL error, and archive fails with psql's
message. The load is not one transaction, because CREATE DATABASE cannot run
inside one.
A dump import does not replace an existing archive. Loaded onto a server
that already has the archive database, it stops at CREATE DATABASE and
changes nothing. To replace an archive, drop its database first, deliberately,
outside this tool.
A one-shot bootstrap container that is restarted -- docker compose down && up
-- runs archive again against an archive that has since advanced. Use
--if-present to decide what happens, before anything is downloaded:
--if-present |
Behaviour |
|---|---|
import |
download and load regardless. The default. The load then fails on a server that already has the database |
skip |
leave the database alone and exit successfully, downloading nothing |
fail |
leave the database alone and exit with an error |
$ mina-provision archive --network mainnet --pg-uri postgres://… --if-present=skip
Database "archive" already holds an archive: 548146 blocks, highest at 548146.
Nothing was downloaded or changed (--if-present=skip).
The check looks at the database the dump restores into, not at the one
--pg-uri names. It treats that database as empty only on positive
evidence: the database does not exist, it has no blocks table, or the
table has no rows. A first run on a new server therefore proceeds.
When the check cannot be made -- the server cannot be reached, is still
starting, or refuses to show the blocks table -- archive fails and
downloads nothing. It keeps trying for up to a minute while the server cannot
be reached, so a compose stack that restarts PostgreSQL and the bootstrap
together sorts itself out.
Fetches precomputed block files for a height range.
| Range | Meaning |
|---|---|
--range 50000 |
the blocks at height 50000 |
--range 50000-51000 |
inclusive on both ends |
--range 50000- |
open-ended, up to the chain tip |
A block name embeds the network, the height and the state hash, so a height on its own narrows the name only to a prefix, which the provider is then asked to list. One height can yield several blocks when the chain had competing blocks at that height.
An open-ended range stops after 1000 consecutive heights with no block. A single run fetches at most 50000 blocks, and a closed range can cover at most 50000 heights. Heights must be from 0 to 9223372036854775806.
Fetches the runtime configuration a daemon auto-loads.
| Flag | Meaning |
|---|---|
--out |
directory to write into |
--version |
exact package version; default is the highest |
--repository, --component, --codename, --package |
override the provider's repository settings |
--ref |
git branch, tag or commit, for a provider that serves a source tree. Letters, digits and . _ / + @ - only; no .. segment, no leading / or - |
The default provider serves this as a Debian package rather than as a plain
file, because the daemon auto-loads /var/lib/coda/config_<hash>.json, and
that hash is derived from the commit at build time. It is not the hash in the
package version:
package version 3.4.0-bd0fe9e (7 characters)
file inside it config_bd0fe9e9.json (9 characters)
The same JSON exists in the mina source tree as
genesis_ledgers/<network>.json. --provider github --ref <tag> fetches it
from there, but it arrives under the source-tree name, which the daemon does
not auto-load.
With no --version, the highest version is chosen by Debian ordering — the
package apt itself would install. Debian ordering is not upload order, so a
build with a longer version string can outrank a newer one. Pin with
--version when a specific build is meant.
Fetches the fork configuration a chain starts from: the fork point, the ledger and epoch ledger hashes, and the genesis timestamp. An archive node and the replayer both need it to start.
It names no accounts, so it stays about a kilobyte however large the ledger is. Whichever program consumes it resolves the ledger from the hashes it carries, downloading and verifying the ledger itself.
It is regenerated as the chain advances, so the command reports which fork point arrived:
Wrote ./mainnet-ledger.json
fork state hash: 3NKHyxzgM9z4C1wUz5ZhJBmNhtDzDQGr1PGvWRwiCUcFvsdTco2T
blockchain length: 548146
global slot since genesis: 958440
This is a different artifact from daemon-config. That one is the runtime
configuration a daemon auto-loads from its own installation.
Rewrites a fork configuration into the input file the replayer reads.
mina-provision genesis-config --network mainnet
mina-provision replayer-input --from mainnet-ledger.json --out replayer-input.json| Flag | Meaning |
|---|---|
--from |
the fork configuration to convert; required |
--out |
where to write it; - for standard output |
--start-slot |
slot to start replaying from; default 0, a full replay |
--target-state-hash |
state hash to stop at; omitted means run to the end |
The replayer takes three fields, and the ledger it takes is the same type as
the ledger object in a fork configuration, so that object is copied across
unchanged — by its hashes, not expanded into accounts. The replayer resolves a
ledger given only its hash, so this command never reads ledger data and the
result stays small. A configuration that states its accounts converts equally
well: whatever the ledger object holds is what the replayer receives.
Two values are not inferred, because the fork point and the replay range
are different things. --start-slot defaults to 0 rather than to the fork's
slot, and no stop point is set unless --target-state-hash is given.
The epoch ledger hashes and seeds are not carried over. The replayer input has no field for them and the replayer rebuilds epoch ledgers from the archive database; the command says so when it drops them.
Every endpoint, bucket and naming rule is configuration, not code. The Mina
Foundation is the default publisher of these artifacts, not the only possible
one: a mirror, an internal artifact store or a directory on disk is selected
with --provider, with no change to the program.
mina-provision blocks --provider acme-mirror --range 50000-50100The built-in defaults are written in the same schema an operator writes, are embedded in the binary, and are read by the same parser. A custom provider can therefore express everything the defaults express.
docs/providers.md is the full guide: worked examples per
backend, what merging does, and how to add a provider to the built-in
defaults.
In order; the first file found is used.
--provider-config <path>MINA_PROVISION_CONFIG$XDG_CONFIG_HOME/mina-provision/config.yaml/etc/mina-provision/config.yaml
Behaviour change: ./mina-provision.yaml in the current directory is no
longer read automatically. The file can redirect every endpoint, and the tool
is often run from a directory that others can write to, such as a --work-dir
a dump was extracted into. To use a file in the current directory, name it:
--provider-config ./mina-provision.yaml.
The file is merged over the built-in defaults, per provider, per network, per artifact. Adding a mirror therefore does not mean restating the default entries, and those entries keep receiving updates. An artifact that is mentioned is replaced whole, so a partial entry cannot inherit half of the endpoint it replaces.
The configuration is read from disk only, and is never fetched. It states which hosts may supply artifacts, so downloading it would remove the property that makes it worth having.
version: 1
default_provider: acme
providers:
acme:
description: internal mirror
networks:
mainnet:
archive_dump:
backend: http
base_url: https://artifacts.acme.internal/mina
name: "dumps/mainnet-{date}.sql.tar.gz"
checksum: sidecar # expects <file>.sha256 beside it
database: archive # the database the dumps create
precomputed_blocks:
backend: file
path: /srv/mina/blocks
name: "mainnet-{height}-{state_hash}.json"
index: https://artifacts.acme.internal/mina/blocks.txtBackends:
| Backend | Reads from | Can list names |
|---|---|---|
gcs |
a public Google Cloud Storage bucket | yes |
http |
any web server | only with an index: |
file |
a local or mounted directory | yes |
apt |
a Debian repository; daemon_config only |
not applicable |
A web server cannot be enumerated, so blocks needs an index: URL — one
object name per line — from an http provider. Without it the command reports
that discovery is impossible rather than reporting no blocks, which would read
as "the block is not published".
Name templates carry these fields, and a template using any other field is rejected when the configuration is read:
| Artifact | Fields |
|---|---|
archive_dump |
{date}, {hour} |
precomputed_blocks |
{height}, {state_hash} |
daemon_config |
{ref} |
genesis_config |
none — one fixed name per network |
checksum: states how a download is proved to be what the publisher intended.
| Mode | Meaning |
|---|---|
index |
the digest comes from the Debian repository index; apt only |
sidecar |
the digest comes from <file>.sha256 beside the object |
none |
only the transfer itself is checked |
A configured check that cannot be performed is a failure, not a silent pass: a missing sidecar fails the download.
checksum: index proves that the package matches the index. The index itself
is not checked against a signed InRelease file, so it is as trustworthy as
the https connection and the repository host, and no more. It does not protect
against a compromised mirror.
base_url, index and repository must be https:// URLs. Plain http://
is accepted only for a loopback host (localhost, 127.0.0.0/8, ::1), or
when the artifact sets insecure: true, which makes that choice visible in the
configuration. A redirect from https to plain http is always refused.
A request fails when the response headers do not arrive within 30 s, or when
one read of the body receives no data for 60 s. There is no limit on the total
time, so a large dump on a slow link still completes. SIGINT and SIGTERM stop
the downloads and psql, and the command exits non-zero. See
docs/providers.md.
This tool fetches, verifies and places files. It does not write blocks into an archive database, and it does not repair gaps.
Inserting a precomputed block is not a file copy: the block is decoded, its hashes are derived, and about twenty related tables are written. That logic belongs to the archive writer, and a second implementation of it in another language would diverge silently.
| Concern | Owner |
|---|---|
| Where artifacts are, and whether they are genuine | mina-provision |
| What is missing from a database, and writing it | mina-archive |
| Verifying a block's proof | mina-verify |
Dumps are produced hourly, so a restored database is behind the chain tip.
Fetch the difference with mina-provision blocks and apply it with
mina-archive, which takes a local directory of block files as input.
| Variable | Flag |
|---|---|
MINA_NETWORK |
--network |
MINA_PROVIDER |
--provider |
MINA_PROVISION_CONFIG |
--provider-config |
A flag on the command line always wins over the environment, and an empty variable is treated as unset.
go build ./...
go vet ./...
go test ./... # no network access
go test -tags integration ./... # needs PostgreSQL and live publishersThe default test suite never touches the network. A daily workflow runs the live checks against the default provider, so a bucket that is renamed or a naming rule that changes is noticed in this repository rather than by an operator.
Pushing a vX.Y.Z tag builds a static binary for amd64 and arm64, packages
each as a .deb, attaches them to a GitHub Release, and publishes the signed
packages to the stable component of stable.apt.packages.minaprotocol.com.
amd64 goes to bullseye, focal, jammy, bookworm and noble; arm64 goes to
bookworm and noble, the two distributions that declare it. The release also
has a SHA256SUMS file and a provenance attestation for its files.
Publication is described in docs/releasing.md.
ledger— genesis and epoch ledger tarballs. A ledger tarball's identity is its sha3 value, equal tos3_data_hash, while its file name carries a blake2 hash.checkpoint— replayer checkpoints.peers— seed and peer lists.- Signature verification, beyond the published checksum.
- An
s3backend.