forked from VijitSingh97/bitcoin-starter-stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstack
More file actions
executable file
·217 lines (199 loc) · 7.81 KB
/
Copy pathstack
File metadata and controls
executable file
·217 lines (199 loc) · 7.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#!/usr/bin/env bash
# Single entry point for operating the stack. Run ./stack help for usage.
set -euo pipefail
cd "$(dirname "$0")"
usage() {
cat <<'EOF'
Usage: ./stack <command> [args]
up | down | restart [svc] Start / stop / restart the stack (or one service)
init Interactive setup wizard (optional; up uses defaults)
logs [svc] Follow logs (all services, or one)
apply Re-render .env from config.json and apply changes
status Container + health summary; warns on anything down
doctor Read-only health report (deps, disk, sync, tor)
backup Save config, credentials, and onion keys to backups/
restore [-y] <archive> Restore a backup archive (prompts before overwrite)
help This text
EOF
}
env_get() { sed -n "s/^$1=//p" .env 2>/dev/null; }
# Interactive setup: every prompt has a sensible default — press Enter to skip.
# Writes config.json (only the values you change), then offers to start. Plain
# `./stack up` needs none of this; it just uses the defaults.
init() {
if [ -f config.json ]; then
printf "config.json already exists — overwrite it? [y/N] "
read -r reply
case "$reply" in y | Y) ;; *)
echo "Kept existing config.json. (Edit it and ./stack apply, or ./stack up.)"
return 0
;;
esac
fi
echo "Setup — press Enter to accept the [default]. Nothing here is required."
echo
read -r -p "RPC username [bitcoin]: " w_user
read -rsp "RPC password [Enter = strong random]: " w_pass; echo
read -rsp "Dashboard web password [Enter = no login]: " w_dashpass; echo
read -r -p "Publish the dashboard as a Tor onion service (reach it from anywhere)? [y/N] " w_dashonion
read -r -p "Accept inbound connections over Tor (helps the network, no IP exposure)? [y/N] " w_inbound
read -r -p "Sync the initial download over clearnet first? Faster, but EXPOSES YOUR IP. [y/N] " w_clearnet
read -r -p "Prune the chain to save disk — size in GB, or Enter for a full node: " w_prune_gb
yes() { case "$1" in y | Y | yes | YES) return 0 ;; *) return 1 ;; esac; }
cfg=$(jq -n '{}')
[ -n "$w_user" ] && [ "$w_user" != "bitcoin" ] && cfg=$(jq --arg v "$w_user" '.bitcoin.node_username=$v' <<<"$cfg")
[ -n "$w_pass" ] && cfg=$(jq --arg v "$w_pass" '.bitcoin.node_password=$v' <<<"$cfg")
[ -n "$w_dashpass" ] && cfg=$(jq --arg v "$w_dashpass" '.dashboard.password=$v' <<<"$cfg")
yes "$w_dashonion" && cfg=$(jq '.dashboard.onion=true' <<<"$cfg")
yes "$w_inbound" && cfg=$(jq '.bitcoin.inbound_onion=true' <<<"$cfg")
yes "$w_clearnet" && cfg=$(jq '.bitcoin.sync_over_clearnet=true' <<<"$cfg")
if [ -n "$w_prune_gb" ]; then
case "$w_prune_gb" in
*[!0-9]*) echo " (prune must be a whole number of GB — ignoring)" ;;
*) cfg=$(jq --argjson v "$((w_prune_gb * 1000))" '.bitcoin.prune_mb=$v' <<<"$cfg") ;;
esac
fi
echo "$cfg" | jq . >config.json
echo
echo "Wrote config.json:"
sed 's/^/ /' config.json
echo
printf "Start the stack now? [Y/n] "
read -r reply
case "$reply" in n | N) echo "Later: ./stack up" ;; *)
./configure.sh && docker compose up -d
;;
esac
}
status() {
docker compose ps
bad=0
for svc in tor bitcoin dashboard; do
st=$(docker inspect "$svc" --format '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}}' 2>/dev/null || echo missing)
case "$st" in
"running healthy") echo "OK $svc" ;;
"running health: starting" | "running starting") echo "WAIT $svc (health check warming up)" ;;
*)
echo "WARN $svc: $st"
bad=1
;;
esac
done
return "$bad"
}
doctor() {
echo "== Dependencies"
for tool in docker jq openssl; do
if command -v "$tool" >/dev/null; then echo "OK $tool"; else echo "MISS $tool"; fi
done
echo "== Config"
if [ ! -f config.json ]; then
echo "MISS config.json (cp config.example.json config.json)"
elif [ ! -f .env ]; then
echo "MISS .env (run ./stack apply)"
elif [ config.json -nt .env ]; then
echo "WARN config.json is newer than .env — run ./stack apply"
else
echo "OK config.json rendered into .env"
fi
echo "== Disk"
data_dir=$(env_get BITCOIN_DATA_DIR)
df -h "${data_dir:-.}" 2>/dev/null | tail -1 | awk '{printf " %s free of %s (%s used) at %s\n", $4, $2, $5, $6}'
echo "== Containers"
status || true
echo "== Node"
if docker exec bitcoin bitcoin-cli -datadir=/data getblockchaininfo 2>/dev/null |
jq -r '" blocks \(.blocks)/\(.headers) progress \((.verificationprogress*100*100|round)/100)% ibd \(.initialblockdownload) pruned \(.pruned)"'; then :; else
echo " RPC unreachable (normal during startup)"
fi
echo "== Tor"
docker logs tor 2>&1 | grep "Bootstrapped" | tail -1 | sed 's/^/ /' || echo " no bootstrap log yet"
if [ "$(env_get DASHBOARD_ONION)" = "1" ]; then
echo " dashboard onion: $(docker exec tor cat /var/lib/tor/dashboard_onion/hostname 2>/dev/null || echo 'not provisioned yet')"
fi
if [ "$(env_get BITCOIN_INBOUND_ONION)" = "1" ]; then
echo " node onion: $(docker exec bitcoin bitcoin-cli -datadir=/data getnetworkinfo 2>/dev/null | jq -r '.localaddresses[0].address // "not registered yet"')"
fi
}
do_backup() {
mkdir -p backups
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
[ -f config.json ] && cp config.json "$tmp/"
[ -f .env ] && cp .env "$tmp/"
# bitcoind stores its inbound-onion key in the datadir
data_dir=$(env_get BITCOIN_DATA_DIR)
if [ -n "$data_dir" ] && [ -f "$data_dir/onion_v3_private_key" ]; then
cp "$data_dir/onion_v3_private_key" "$tmp/"
fi
# the dashboard onion keys live in the tor volume
docker compose cp tor:/var/lib/tor/dashboard_onion "$tmp/dashboard_onion" >/dev/null 2>&1 || true
out="backups/stack-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
tar czf "$out" -C "$tmp" .
chmod 600 "$out"
echo "Wrote $out (config, credentials, onion keys — chain data excluded, it's re-derivable)"
}
do_restore() {
yes=0
if [ "${1:-}" = "-y" ]; then
yes=1
shift
fi
archive=${1:?usage: ./stack restore [-y] <archive>}
if [ "$yes" != "1" ]; then
printf "This overwrites config.json, .env, and onion keys from %s. Continue? [y/N] " "$archive"
read -r reply
case "$reply" in y | Y) ;; *)
echo "Aborted."
exit 1
;;
esac
fi
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
tar xzf "$archive" -C "$tmp"
[ -f "$tmp/config.json" ] && cp "$tmp/config.json" config.json && echo "Restored config.json"
if [ -f "$tmp/.env" ]; then
cp "$tmp/.env" .env && chmod 600 .env && echo "Restored .env"
fi
data_dir=$(env_get BITCOIN_DATA_DIR)
if [ -f "$tmp/onion_v3_private_key" ] && [ -n "$data_dir" ]; then
cp "$tmp/onion_v3_private_key" "$data_dir/" && echo "Restored node onion key"
fi
if [ -d "$tmp/dashboard_onion" ]; then
if docker compose cp "$tmp/dashboard_onion" tor:/var/lib/tor/ >/dev/null 2>&1 &&
docker compose exec -u root tor chown -R tor:tor /var/lib/tor/dashboard_onion >/dev/null 2>&1; then
docker compose restart tor >/dev/null
echo "Restored dashboard onion keys (tor restarted)"
else
echo "WARN: could not restore dashboard onion keys (is the tor container up?)"
fi
fi
echo "Done. Apply with: ./stack apply"
}
cmd=${1:-help}
[ $# -gt 0 ] && shift
case "$cmd" in
up)
[ -f .env ] || ./configure.sh # first run: generate .env (auto-config)
docker compose up -d "$@"
;;
init) init ;;
down) docker compose down "$@" ;;
restart) docker compose restart "$@" ;;
logs) docker compose logs -f --tail=100 "$@" ;;
apply)
./configure.sh
docker compose up -d
;;
status) status ;;
doctor) doctor ;;
backup) do_backup ;;
restore) do_restore "$@" ;;
help | --help | -h) usage ;;
*)
echo "Unknown command: $cmd"
usage
exit 1
;;
esac