Skip to content

Commit 078015f

Browse files
committed
feat: implement backup library for local and Docker execution modes
1 parent dfaed99 commit 078015f

3 files changed

Lines changed: 317 additions & 32 deletions

File tree

scripts/reth-backup/README.md

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,59 @@
33
Script to snapshot the `ev-reth` MDBX database while the node keeps running and
44
record the block height contained in the snapshot.
55

6+
The script supports two execution modes:
7+
8+
- **local**: Backup a reth instance running directly on the host machine
9+
- **docker**: Backup a reth instance running in a Docker container
10+
611
## Prerequisites
712

8-
- Docker access to the container running `ev-reth` (defaults to the service name
9-
`ev-reth` from `docker-compose`).
10-
- The `mdbx_copy` binary available inside that container. If it is not provided
11-
by the image, compile it once inside the container (see [libmdbx
13+
### Common requirements
14+
15+
- The `mdbx_copy` binary available in the target environment (see [libmdbx
1216
documentation](https://libmdbx.dqdkfa.ru/)).
1317
- `jq` installed on the host to parse the JSON output.
1418

19+
### Docker mode
20+
21+
- Docker access to the container running `ev-reth` (defaults to the service name
22+
`ev-reth` from `docker-compose`).
23+
24+
### Local mode
25+
26+
- Direct filesystem access to the reth datadir.
27+
- Sufficient permissions to read the database files.
28+
1529
## Usage
1630

31+
### Local mode
32+
33+
When reth is running directly on your machine:
34+
35+
```bash
36+
./scripts/reth-backup/backup.sh \
37+
--mode local \
38+
--datadir /var/lib/reth \
39+
--mdbx-copy /usr/local/bin/mdbx_copy \
40+
/path/to/backups
41+
```
42+
43+
### Docker mode
44+
45+
When reth is running in a Docker container:
46+
1747
```bash
1848
./scripts/reth-backup/backup.sh \
49+
--mode docker \
1950
--container ev-reth \
2051
--datadir /home/reth/eth-home \
2152
--mdbx-copy /tmp/libmdbx/build/mdbx_copy \
2253
/path/to/backups
2354
```
2455

25-
This creates a timestamped folder under `/path/to/backups` with:
56+
### Output structure
57+
58+
Both modes create a timestamped folder under `/path/to/backups` with:
2659

2760
- `db/mdbx.dat` – consistent MDBX snapshot.
2861
- `db/mdbx.lck` – placeholder lock file (empty).
@@ -33,39 +66,64 @@ This creates a timestamped folder under `/path/to/backups` with:
3366
Additional flags:
3467

3568
- `--tag LABEL` to override the timestamped folder name.
36-
- `--keep-remote` to leave the temporary snapshot inside the container (useful
37-
for debugging).
69+
- `--keep-remote` to leave the temporary snapshot in the target environment
70+
(useful for debugging).
3871

3972
The script outputs the height at the end so you can coordinate other backups
4073
with the same block number.
4174

42-
## End-to-end workflow with `apps/evm/single`
75+
## Architecture
76+
77+
The backup script is split into two components:
78+
79+
- **`backup-lib.sh`**: Abstract execution layer providing a common interface for
80+
different execution modes (local, docker). This library defines functions like
81+
`exec_remote`, `copy_from_remote`, `copy_to_remote`, and `cleanup_remote`
82+
that are implemented differently for each backend.
83+
- **`backup.sh`**: Main script that uses the library and orchestrates the backup
84+
workflow. It's mode-agnostic and works with any backend that implements the
85+
required interface.
86+
87+
This separation allows easy extension to support additional execution
88+
environments (SSH, Kubernetes, etc.) without modifying the core backup logic.
89+
90+
## End-to-end workflow with `apps/evm/single` (Docker mode)
4391

4492
The `evm/single` docker-compose setup expects the backup image to reuse the
4593
`ghcr.io/evstack/ev-reth:latest` tag. To capture both the MDBX and ev-node
4694
Badger backups end-to-end:
4795

4896
1. Build the helper image so `ev-reth` has the MDBX tooling preinstalled.
97+
4998
```bash
5099
docker build -t ghcr.io/evstack/ev-reth:latest scripts/reth-backup
51100
```
101+
52102
2. Build the `ev-node-evm-single` image so it includes the latest CLI backup
53103
command (optional if you already pushed the binary and re-tagged it).
104+
54105
```bash
55106
docker build -t ghcr.io/evstack/ev-node-evm-single:main -f apps/evm/single/Dockerfile .
56107
```
108+
57109
3. Start the stack.
110+
58111
```bash
59112
(cd apps/evm/single && docker compose up -d)
60113
```
114+
61115
4. Run the MDBX snapshot script (adjust the destination as needed).
116+
62117
```bash
63-
./scripts/reth-backup/backup.sh backups/full-run/reth
118+
./scripts/reth-backup/backup.sh --mode docker backups/full-run/reth
64119
```
120+
65121
The script prints the generated tag (for example `20251013-104816`) and the
66122
captured height (stored under
67123
`backups/full-run/reth/<TAG>/height.txt`).
124+
68125
5. Align the ev-node datastore to that height and take the Badger backup:
126+
69127
```bash
70128
HEIGHT=$(cat backups/full-run/reth/<TAG>/height.txt)
71129
BACKUP_ROOT="$(pwd)/backups/full-run"
@@ -123,12 +181,16 @@ EOF
123181
-c "/tmp/evnode_backup.sh /host-backup/evnode-backup-aligned.badger ${HEIGHT}")
124182

125183
rm /tmp/evnode_backup.sh
184+
126185
# Bring the managed container back with its usual supervisor.
127186
(cd apps/evm/single && docker compose start ev-node-evm-single)
128187
```
188+
129189
The CLI will report the streamed metadata, and the backup lands at
130190
`backups/full-run/ev-node/evnode-backup-aligned.badger`.
191+
131192
6. When finished, tear the stack down.
193+
132194
```bash
133195
(cd apps/evm/single && docker compose down)
134196
```

scripts/reth-backup/backup-lib.sh

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#!/usr/bin/env bash
2+
3+
# backup-lib.sh - Abstract execution layer for reth backup operations
4+
# Provides a common interface for local and Docker-based executions.
5+
6+
# Backend interface that must be implemented:
7+
# - exec_remote <command> Execute a command in the target environment
8+
# - copy_from_remote <src> <dst> Copy a file/directory from target to local
9+
# - copy_to_remote <src> <dst> Copy a file/directory from local to target
10+
# - cleanup_remote <path> Remove a path in the target environment
11+
12+
# ============================================================================
13+
# LOCAL BACKEND
14+
# ============================================================================
15+
16+
local_exec_remote() {
17+
bash -c "$1"
18+
}
19+
20+
local_copy_from_remote() {
21+
local src="$1"
22+
local dst="$2"
23+
cp -a "$src" "$dst"
24+
}
25+
26+
local_copy_to_remote() {
27+
local src="$1"
28+
local dst="$2"
29+
cp -a "$src" "$dst"
30+
}
31+
32+
local_cleanup_remote() {
33+
local path="$1"
34+
rm -rf "$path"
35+
}
36+
37+
local_check_available() {
38+
# Always available
39+
return 0
40+
}
41+
42+
# ============================================================================
43+
# DOCKER BACKEND
44+
# ============================================================================
45+
46+
docker_exec_remote() {
47+
local container="$BACKEND_CONTAINER"
48+
docker exec "$container" bash -lc "$1"
49+
}
50+
51+
docker_copy_from_remote() {
52+
local container="$BACKEND_CONTAINER"
53+
local src="$1"
54+
local dst="$2"
55+
docker cp "${container}:${src}" "$dst"
56+
}
57+
58+
docker_copy_to_remote() {
59+
local container="$BACKEND_CONTAINER"
60+
local src="$1"
61+
local dst="$2"
62+
docker cp "$src" "${container}:${dst}"
63+
}
64+
65+
docker_cleanup_remote() {
66+
local container="$BACKEND_CONTAINER"
67+
local path="$1"
68+
docker exec "$container" rm -rf "$path"
69+
}
70+
71+
docker_check_available() {
72+
if ! command -v docker >/dev/null 2>&1; then
73+
echo "error: docker command not found" >&2
74+
return 1
75+
fi
76+
77+
local container="$BACKEND_CONTAINER"
78+
if [[ -z "$container" ]]; then
79+
echo "error: container name is required for docker mode" >&2
80+
return 1
81+
fi
82+
83+
if ! docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
84+
echo "error: container '$container' is not running" >&2
85+
return 1
86+
fi
87+
88+
return 0
89+
}
90+
91+
# ============================================================================
92+
# BACKEND INITIALIZATION
93+
# ============================================================================
94+
95+
# Set the backend mode and initialize function pointers
96+
init_backend() {
97+
local mode="$1"
98+
99+
case "$mode" in
100+
local)
101+
exec_remote=local_exec_remote
102+
copy_from_remote=local_copy_from_remote
103+
copy_to_remote=local_copy_to_remote
104+
cleanup_remote=local_cleanup_remote
105+
check_backend_available=local_check_available
106+
;;
107+
docker)
108+
exec_remote=docker_exec_remote
109+
copy_from_remote=docker_copy_from_remote
110+
copy_to_remote=docker_copy_to_remote
111+
cleanup_remote=docker_cleanup_remote
112+
check_backend_available=docker_check_available
113+
;;
114+
*)
115+
echo "error: unknown backend mode '$mode'" >&2
116+
echo "supported modes: local, docker" >&2
117+
return 1
118+
;;
119+
esac
120+
121+
BACKEND_MODE="$mode"
122+
return 0
123+
}
124+
125+
# ============================================================================
126+
# HIGH-LEVEL BACKUP OPERATIONS
127+
# ============================================================================
128+
129+
# Verify that a command is available in the target environment
130+
verify_remote_command() {
131+
local cmd="$1"
132+
if ! $exec_remote "command -v '$cmd' >/dev/null 2>&1 || [ -x '$cmd' ]"; then
133+
echo "error: command '$cmd' not found in target environment" >&2
134+
return 1
135+
fi
136+
return 0
137+
}
138+
139+
# Create a directory in the target environment
140+
create_remote_dir() {
141+
local path="$1"
142+
$exec_remote "mkdir -p '$path'"
143+
}
144+
145+
# Check if a path exists in the target environment
146+
remote_path_exists() {
147+
local path="$1"
148+
$exec_remote "test -e '$path'"
149+
}
150+
151+
# Run mdbx_copy in the target environment
152+
run_mdbx_copy() {
153+
local mdbx_copy="$1"
154+
local source_db="$2"
155+
local dest_file="$3"
156+
157+
echo "Running mdbx_copy..."
158+
$exec_remote "'$mdbx_copy' -c '$source_db' '$dest_file'"
159+
}
160+
161+
# Query ev-reth for stage checkpoints
162+
query_stage_checkpoints() {
163+
local datadir="$1"
164+
$exec_remote "ev-reth db --datadir '$datadir' list StageCheckpoints --len 20 --json" | sed -n '/^\[/,$p'
165+
}

0 commit comments

Comments
 (0)