Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
241 lines (197 loc) · 9.29 KB
/
Copy pathMakefile
File metadata and controls
241 lines (197 loc) · 9.29 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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
ENV ?= testnet
INV := inventory/$(ENV).yml
VAULT := group_vars/vault-$(ENV).yml
H := /home/monad
CLOG := $(H)/log/monad-consensus.log
VELOG := $(H)/execution/log/monad-execution.log
FELOG := $(H)/execution/log/monad-fullnode-execution.log
RLOG := $(H)/log/monad-rpc.log
A := -i $(INV) $(if $(NODE),--limit $(NODE),)
DR := $(if $(DRYRUN),--check --diff,)
$(if $(wildcard $(INV)),,$(error Inventory not found: $(INV) — valid ENVs: testnet, mainnet))
define confirm
@if [ "$(CONFIRM)" != "yes" ]; then \
echo "Refusing destructive target '$@'. Re-run with CONFIRM=yes (and DRYRUN=1 if you want --check --diff first)."; \
exit 1; \
fi
endef
# Resolve a single ansible_host IP: if NODE=name is set, ask inventory about that
# host; else fall back to first host in validators group.
define node_ip
$$(if [ -n "$(NODE)" ]; then \
ansible-inventory -i $(INV) --host $(NODE) 2>/dev/null | jq -r '.ansible_host // .ansible_ssh_host'; \
else \
ansible-inventory -i $(INV) --list 2>/dev/null | jq -r '.validators.hosts[0] as $$h | .["_meta"]["hostvars"][$$h]["ansible_host"]'; \
fi)
endef
# Resolve node type (validator|fullnode) so log commands pick the right exec log.
define node_type
$$(if [ -n "$(NODE)" ]; then \
ansible-inventory -i $(INV) --host $(NODE) 2>/dev/null | jq -r '.type // "validator"'; \
else \
echo validator; \
fi)
endef
## Deployment
bootstrap: ## Bootstrap new validator: generate keys + vault [NAME=foo-testnet]
$(if $(NAME),,$(error NAME= required, e.g. NAME=kiwi-testnet))
@NAME=$(NAME) $(if $(filter command\ line environment,$(origin ENV)),ENV=$(ENV),) ./scripts/bootstrap.sh
deploy: ## Deploy validator
ansible-playbook $(A) $(DR) playbooks/deploy-validator.yml
snapshot: ## Apply snapshot for fast sync
ansible-playbook $(A) $(DR) playbooks/snapshot.yml
execution: ## Setup execution layer
ansible-playbook $(A) $(DR) playbooks/setup-execution.yml
rpc: ## Setup JSON-RPC server
ansible-playbook $(A) $(DR) playbooks/setup-rpc.yml
register: ## Register validator (requires synced node + 100k MON)
ansible-playbook $(A) $(DR) playbooks/register-validator.yml
upgrade: ## Upgrade monad packages (destructive; requires CONFIRM=yes)
$(call confirm)
ansible-playbook $(A) $(DR) playbooks/upgrade-node.yml
page-migrate: ## MIP-8 Phase A dual-DB page migration (destructive; requires CONFIRM=yes)
$(call confirm)
ansible-playbook $(A) $(DR) playbooks/page-migration.yml
page-decommission: ## MIP-8 Phase C slot decommission (destructive; requires CONFIRM=yes)
$(call confirm)
ansible-playbook $(A) $(DR) playbooks/page-decommission.yml
observability: ## Deploy observability stack (Prometheus + Grafana)
ansible-playbook $(A) $(DR) playbooks/setup-observability.yml
## Monitoring
status: ## Show validator status [ENV=] [NODE=]
@MONAD_INV=$(INV) ./scripts/validator-info.sh "$(NODE)"
health: ## Run health checks
ansible-playbook $(A) playbooks/maintenance.yml --tags health
panic-check: ## Scan recent consensus journals for panic patterns [NODE=]
@bash -o pipefail -c 'ansible $(A) validators:fullnodes -m shell -a \
"set -e; \
n=\$$(systemctl show monad-consensus -p NRestarts --value); \
p=\$$(journalctl -u monad-consensus --since \"10 minutes ago\" --no-pager \
| grep -cE \"high qc too far ahead|block tree root|panicked at .*monad-consensus\" || true); \
echo \"NRestarts=\$$n (cumulative, informational) PanicMatches=\$$p (last 10m)\"; \
[ \"\$$p\" -eq 0 ]" \
2>/dev/null | ./scripts/colorize-logs.sh'
logs: ## Tail logs [SVC=consensus|execution|rpc] [LINES=50] [NODE=]
$(eval SVC := $(or $(SVC),consensus))
$(eval L := $(or $(LINES),50))
@TYPE=$(call node_type); \
EXEC_LOG=$$([ "$$TYPE" = "fullnode" ] && echo "$(FELOG)" || echo "$(VELOG)"); \
case "$(SVC)" in \
execution) LOGPATH=$$EXEC_LOG ;; \
rpc) LOGPATH="$(RLOG)" ;; \
*) LOGPATH="$(CLOG)" ;; \
esac; \
ansible $(A) validators:fullnodes -m shell -a "tail -$(L) $$LOGPATH" | ./scripts/colorize-logs.sh
watch: ## Stream logs [SVC=consensus|execution|rpc] [NODE=]
$(eval SVC := $(or $(SVC),consensus))
@IP=$(call node_ip); \
TYPE=$(call node_type); \
EXEC_LOG=$$([ "$$TYPE" = "fullnode" ] && echo "$(FELOG)" || echo "$(VELOG)"); \
case "$(SVC)" in \
execution) LOGPATH=$$EXEC_LOG ;; \
rpc) LOGPATH="$(RLOG)" ;; \
*) LOGPATH="$(CLOG)" ;; \
esac; \
ssh root@$$IP "tail -f $$LOGPATH" 2>/dev/null | ./scripts/colorize-logs.sh
## Operations
restart: ## Restart services (execution → consensus → rpc)
ansible-playbook $(A) $(DR) playbooks/maintenance.yml --tags restart
stop: ## Stop services (destructive; requires CONFIRM=yes)
$(call confirm)
ansible-playbook $(A) $(DR) playbooks/maintenance.yml --tags stop
start: ## Start services
ansible-playbook $(A) $(DR) playbooks/maintenance.yml --tags start
commission: ## Set commission rate [RATE=20] [NODE=]
@ansible $(A) validators -b --become-user monad -m shell -a '$(H)/scripts/set-commission.sh $(or $(RATE),20)'
claim: ## Claim validator rewards [NODE=]
@ansible $(A) validators -b --become-user monad -m shell -a '$(H)/scripts/claim-rewards.sh'
compound: ## Compound rewards (claim + restake) [NODE=]
@ansible $(A) validators -b --become-user monad -m shell -a '$(H)/scripts/compound-rewards.sh'
auto-compound: ## Enable auto-compound timer [SCHEDULE="*-*-* 08:00:00"] [NODE=]
ansible-playbook $(A) playbooks/maintenance.yml --tags auto-compound \
$(if $(SCHEDULE),-e compound_rewards_schedule="$(SCHEDULE)",) \
-e compound_rewards_enabled=true
## Backup
backup-config: ## Backup config on remote server [NODE=]
ansible-playbook $(A) playbooks/maintenance.yml --tags backup
backup-keys: ## Download validator keystores to secrets/ [NODE=]
@ansible $(A) validators -b --become-user monad -m fetch \
-a 'src=$(H)/key/id-secp dest=secrets/{{ inventory_hostname }}-id-secp flat=yes'
@ansible $(A) validators -b --become-user monad -m fetch \
-a 'src=$(H)/key/id-bls dest=secrets/{{ inventory_hostname }}-id-bls flat=yes'
@echo "" && echo "Keys saved to secrets/"
## Migration
migrate: ## Fast migrate validator [OLD=name] [NEW=name] (pre-deploy new node first)
$(if $(OLD),,$(error OLD= required (source node name)))
$(if $(NEW),,$(error NEW= required (destination node name)))
ansible-playbook $(A) $(DR) playbooks/migrate-validator.yml -e old_node=$(OLD) -e new_node=$(NEW)
## MEV
fastlane: ## Deploy FastLane sidecar [NODE=]
ansible-playbook $(A) $(DR) playbooks/setup-fastlane.yml
sidecar-health: ## Check sidecar health [NODE=]
@ansible $(A) validators -m shell -a 'curl -s http://localhost:8765/health | jq' 2>/dev/null | ./scripts/colorize-logs.sh
## Recovery
recovery: ## Run recovery playbook (destructive; requires CONFIRM=yes)
$(call confirm)
ansible-playbook $(A) $(DR) playbooks/recovery.yml
diagnose: ## Show diagnostic info
ansible-playbook $(A) playbooks/recovery.yml --tags diagnose
## Utilities
ping: ## Test connectivity
@ansible $(A) all -m ping
grafana: ## Open Grafana via SSH tunnel [NODE=] [PORT=3030]
@IP=$(call node_ip); \
NAME=$$(echo "$(NODE)" | grep -q . && echo "$(NODE)" || echo "first validator"); \
LPORT=$${PORT:-3030}; \
if lsof -iTCP:$$LPORT -sTCP:LISTEN -P -n >/dev/null 2>&1; then \
HOLDER=$$(lsof -iTCP:$$LPORT -sTCP:LISTEN -P -n | tail -n +2 | head -1); \
echo "✗ local port $$LPORT already in use:"; \
echo " $$HOLDER"; \
if echo "$$HOLDER" | grep -q "^ssh "; then \
echo " Looks like a previous tunnel. Kill it: pkill -f 'ssh -N -L $$LPORT'"; \
else \
echo " Pass a free port: make grafana PORT=3031 NODE=$(NODE)"; \
fi; \
exit 1; \
fi; \
echo "Grafana on $$NAME ($$IP) → http://localhost:$$LPORT"; \
echo "Login: admin / (see vault_grafana_admin_password)"; \
echo "Press Ctrl+C to close tunnel"; \
ssh -N -L $$LPORT:127.0.0.1:3000 root@$$IP
hardware: ## Show hardware specs (CPU, RAM, storage)
@MONAD_INV=$(INV) ./scripts/hardware-info.sh "$(NODE)"
speedtest: ## Run bandwidth speedtest
@MONAD_INV=$(INV) ./scripts/speedtest.sh "$(NODE)"
ssh: ## SSH to validator [NODE=]
@IP=$(call node_ip); ssh root@$$IP
check: ## Syntax check all playbooks
@rc=0; \
for pb in playbooks/*.yml; do \
printf " %-40s" "$$pb"; \
if ansible-playbook $(A) $$pb --syntax-check > /dev/null; then \
echo "✓"; \
else \
echo "✗"; rc=1; \
fi; \
done; \
exit $$rc
## Vault
vault-edit: ## Edit vault secrets [ENV=]
ansible-vault edit $(VAULT)
vault-encrypt: ## Encrypt vault [ENV=]
ansible-vault encrypt $(VAULT)
vault-decrypt: ## Decrypt vault [ENV=]
ansible-vault decrypt $(VAULT)
## Help
help:
@echo "Usage: make [target] [ENV=testnet|mainnet] [NODE=name] [DRYRUN=1] [CONFIRM=yes]"
@echo ""
@echo " ENV defaults to 'testnet', uses inventory/\$$ENV.yml"
@echo " DRYRUN=1 passes --check --diff to ansible-playbook"
@echo " CONFIRM=yes required for destructive targets: upgrade, page-migrate, page-decommission, stop, recovery"
@echo ""
@awk '/^## /{sub(/^## /,""); printf "\n\033[1m%s\033[0m\n", $$0; next} \
/^[a-z-]+:.*##/{split($$0,a,":.*## "); printf " \033[36m%-12s\033[0m %s\n", a[1], a[2]}' $(MAKEFILE_LIST)
@echo ""
.DEFAULT_GOAL := help
.PHONY: bootstrap deploy snapshot execution rpc register upgrade page-migrate page-decommission observability fastlane sidecar-health status health panic-check logs watch restart stop start commission claim compound auto-compound backup-config backup-keys migrate recovery diagnose ping grafana hardware speedtest ssh check vault-edit vault-encrypt vault-decrypt help