-
Notifications
You must be signed in to change notification settings - Fork 21
409 lines (360 loc) · 15.4 KB
/
create-devnet.yml
File metadata and controls
409 lines (360 loc) · 15.4 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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
name: Create Devnet
on:
workflow_dispatch:
inputs:
devnet_name:
description: "Devnet name (without 'devnet-' prefix, e.g. 'mytest' creates 'devnet-mytest')"
required: true
type: string
platform_version:
description: "Platform/dashmate version to deploy (e.g. 2.0.0-rc.16)"
required: true
type: string
default: "2.0.0-rc.16"
deploy_tags:
description: "Ansible tags to run. Use full_deploy for full flow, or a narrower tag such as unban_hp_masternodes to resume faster."
required: true
type: string
default: "full_deploy"
# Advanced options - sane defaults, only change if you know what you're doing
hp_masternodes_arm_count:
description: "Advanced: Number of ARM HP masternodes"
required: false
type: string
default: "11"
hp_masternodes_amd_count:
description: "Advanced: Number of AMD HP masternodes"
required: false
type: string
default: "0"
masternodes_arm_count:
description: "Advanced: Number of ARM regular masternodes"
required: false
type: string
default: "0"
masternodes_amd_count:
description: "Advanced: Number of AMD regular masternodes"
required: false
type: string
default: "0"
seed_count:
description: "Advanced: Number of seed nodes"
required: false
type: string
default: "1"
core_version:
description: "Advanced: Core (dashd) image version (leave empty for default)"
required: false
type: string
default: ""
hpmn_disk_size:
description: "Advanced: HP masternode disk size in GB"
required: false
type: string
default: "30"
jobs:
create:
name: Create Devnet
runs-on: ubuntu-22.04
timeout-minutes: 120
concurrency:
group: "devnet-${{ github.event.inputs.devnet_name }}"
cancel-in-progress: false
env:
NETWORK_NAME: "devnet-${{ github.event.inputs.devnet_name }}"
DEVNET_ONLY_GUARD: "true"
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
TERRAFORM_S3_BUCKET: ${{ secrets.TERRAFORM_S3_BUCKET }}
TERRAFORM_S3_KEY: ${{ secrets.TERRAFORM_S3_KEY }}
TERRAFORM_DYNAMODB_TABLE: ${{ secrets.TERRAFORM_DYNAMODB_TABLE }}
ANSIBLE_HOST_KEY_CHECKING: "false"
steps:
- name: Validate devnet name
env:
NAME: ${{ github.event.inputs.devnet_name }}
run: |
if [[ -z "$NAME" ]]; then
echo "Error: devnet_name is required"
exit 1
fi
if [[ "$NAME" =~ ^devnet- ]]; then
echo "Error: Do not include 'devnet-' prefix. Just provide the name (e.g. 'mytest')"
exit 1
fi
if [[ ! "$NAME" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "Error: devnet_name must be lowercase alphanumeric with optional hyphens"
exit 1
fi
if [[ "$NAME" == "testnet" || "$NAME" == "mainnet" || "$NAME" == mainnet-* ]]; then
echo "Error: reserved network names are not allowed in this workflow"
exit 1
fi
if [[ ! "devnet-$NAME" =~ ^devnet-[a-z0-9][a-z0-9-]*$ ]]; then
echo "Error: resulting network name is not a valid devnet name"
exit 1
fi
echo "Will create: devnet-$NAME"
- name: Checkout dash-network-deploy
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Node.js dependencies
run: npm ci
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.12.1"
terraform_wrapper: false
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y python3-pip python3-netaddr sshpass jq
- name: Install Ansible
run: |
python3 -m pip install --upgrade pip
python3 -m pip install ansible-core==2.16.3 jmespath
- name: Install Ansible roles
run: |
ansible-galaxy install -r ansible/requirements.yml
mkdir -p ~/.ansible/roles
cp -r ansible/roles/* ~/.ansible/roles/
- name: Set up SSH keys
env:
DEPLOY_SERVER_KEY: ${{ secrets.DEPLOY_SERVER_KEY }}
EVO_APP_DEPLOY_KEY: ${{ secrets.EVO_APP_DEPLOY_KEY }}
EVO_APP_DEPLOY_WRITE_KEY: ${{ secrets.EVO_APP_DEPLOY_WRITE_KEY }}
run: |
mkdir -p ~/.ssh
# Server SSH key for connecting to nodes
printf '%s\n' "$DEPLOY_SERVER_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
# Derive public key from private key
ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
chmod 644 ~/.ssh/id_rsa.pub
# GitHub deploy key for cloning configs repo
printf '%s\n' "$EVO_APP_DEPLOY_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
# Optional write key for pushing to configs repo
if [[ -n "$EVO_APP_DEPLOY_WRITE_KEY" ]]; then
printf '%s\n' "$EVO_APP_DEPLOY_WRITE_KEY" > ~/.ssh/id_ed25519_write
chmod 600 ~/.ssh/id_ed25519_write
fi
# SSH config
cat > ~/.ssh/config << 'EOL'
Host github.com
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking no
Host *
IdentityFile ~/.ssh/id_rsa
User ubuntu
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null
EOL
chmod 600 ~/.ssh/config
- name: Create networks/.env
run: |
mkdir -p networks
cat > networks/.env << EOF
PRIVATE_KEY_PATH=$HOME/.ssh/id_rsa
PUBLIC_KEY_PATH=$HOME/.ssh/id_rsa.pub
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY
AWS_REGION=$AWS_REGION
TERRAFORM_S3_BUCKET=$TERRAFORM_S3_BUCKET
TERRAFORM_S3_KEY=$TERRAFORM_S3_KEY
TERRAFORM_DYNAMODB_TABLE=$TERRAFORM_DYNAMODB_TABLE
EOF
- name: Check for existing devnet configs
id: existing_configs
run: |
git clone git@github.com:dashpay/dash-network-configs.git /tmp/dash-network-configs-source
FOUND=0
MISSING=0
for ext in yml tfvars inventory; do
SRC="/tmp/dash-network-configs-source/$NETWORK_NAME.$ext"
if [[ -f "$SRC" ]]; then
FOUND=$((FOUND + 1))
else
MISSING=$((MISSING + 1))
fi
done
if [[ $FOUND -eq 3 ]]; then
echo "resume_mode=true" >> "$GITHUB_OUTPUT"
echo "Found existing config set for $NETWORK_NAME. Reusing config repo files and skipping Terraform."
cp "/tmp/dash-network-configs-source/$NETWORK_NAME.yml" networks/
cp "/tmp/dash-network-configs-source/$NETWORK_NAME.tfvars" networks/
cp "/tmp/dash-network-configs-source/$NETWORK_NAME.inventory" networks/
elif [[ $FOUND -eq 0 ]]; then
echo "resume_mode=false" >> "$GITHUB_OUTPUT"
echo "No existing config set found for $NETWORK_NAME. Running full create flow."
else
echo "Error: Partial config set found for $NETWORK_NAME in dash-network-configs."
ls -la /tmp/dash-network-configs-source/$NETWORK_NAME.* 2>/dev/null || true
exit 1
fi
- name: Generate network configs
if: steps.existing_configs.outputs.resume_mode != 'true'
env:
MN_AMD: ${{ github.event.inputs.masternodes_amd_count }}
MN_ARM: ${{ github.event.inputs.masternodes_arm_count }}
HP_AMD: ${{ github.event.inputs.hp_masternodes_amd_count }}
HP_ARM: ${{ github.event.inputs.hp_masternodes_arm_count }}
SEED_COUNT: ${{ github.event.inputs.seed_count }}
run: |
# Validate all counts are numeric
for var in MN_AMD MN_ARM HP_AMD HP_ARM SEED_COUNT; do
val="${!var}"
if [[ ! "$val" =~ ^[0-9]+$ ]]; then
echo "Error: $var must be a number, got '$val'"
exit 1
fi
done
echo "Generating configs for $NETWORK_NAME..."
chmod +x ./bin/generate
./bin/generate "$NETWORK_NAME" \
"$MN_AMD" "$MN_ARM" "$HP_AMD" "$HP_ARM" \
-s="$SEED_COUNT"
echo "Generated config files:"
ls -la networks/devnet-*
- name: Update platform version in config
env:
VERSION: ${{ github.event.inputs.platform_version }}
CORE_VERSION: ${{ github.event.inputs.core_version }}
run: |
CONFIG_FILE="networks/$NETWORK_NAME.yml"
# Escape sed-special characters in version strings
SAFE_VERSION=$(printf '%s' "$VERSION" | sed 's/[&\\/]/\\&/g')
SAFE_CORE_VERSION=$(printf '%s' "$CORE_VERSION" | sed 's/[&\\/]/\\&/g')
echo "Setting platform version to $VERSION..."
# Update dashmate version
sed -i "s/dashmate_version: .*/dashmate_version: $SAFE_VERSION/" "$CONFIG_FILE"
# Update platform service images
sed -i "s|drive_image: dashpay/drive:.*|drive_image: dashpay/drive:$SAFE_VERSION|" "$CONFIG_FILE"
sed -i "s|dapi_image: dashpay/dapi:.*|dapi_image: dashpay/dapi:$SAFE_VERSION|" "$CONFIG_FILE"
# Add rs_dapi_image (not in generated config, but group_vars/all defines it
# without a tag, so we must explicitly set it to get the right version)
if ! grep -q "rs_dapi_image:" "$CONFIG_FILE"; then
echo "rs_dapi_image: dashpay/rs-dapi:$VERSION" >> "$CONFIG_FILE"
else
sed -i "s|rs_dapi_image: dashpay/rs-dapi:.*|rs_dapi_image: dashpay/rs-dapi:$SAFE_VERSION|" "$CONFIG_FILE"
fi
# Update core version if specified
if [[ -n "$CORE_VERSION" ]]; then
echo "Setting core version to $CORE_VERSION..."
sed -i "s|dashd_image: dashpay/dashd:.*|dashd_image: dashpay/dashd:$SAFE_CORE_VERSION|" "$CONFIG_FILE"
fi
echo "Updated config:"
grep -E "(dashmate_version|drive_image|dapi_image|rs_dapi_image|dashd_image)" "$CONFIG_FILE"
- name: Update terraform config
env:
DISK_SIZE: ${{ github.event.inputs.hpmn_disk_size }}
run: |
TFVARS_FILE="networks/$NETWORK_NAME.tfvars"
DEFAULT_MAIN_DOMAIN="networks.dash.org"
# Read current value from file (empty if not set)
CURRENT_SIZE=$(grep -oP 'hpmn_node_disk_size\s*=\s*\K[0-9]+' "$TFVARS_FILE" 2>/dev/null || echo "")
CURRENT_MAIN_DOMAIN=$(grep -oP 'main_domain\s*=\s*"\K[^"]*' "$TFVARS_FILE" 2>/dev/null || echo "")
# Generated tfvars leaves main_domain empty; ensure ACM DNS names are valid.
if [[ -z "$CURRENT_MAIN_DOMAIN" ]]; then
echo "Setting main_domain to $DEFAULT_MAIN_DOMAIN..."
if grep -q '^main_domain\s*=' "$TFVARS_FILE"; then
sed -i "s|^main_domain\\s*=.*|main_domain = \"$DEFAULT_MAIN_DOMAIN\"|" "$TFVARS_FILE"
else
echo "main_domain = \"$DEFAULT_MAIN_DOMAIN\"" >> "$TFVARS_FILE"
fi
fi
if [[ -n "$DISK_SIZE" && "$DISK_SIZE" != "$CURRENT_SIZE" ]]; then
if [[ ! "$DISK_SIZE" =~ ^[0-9]+$ ]]; then
echo "Error: hpmn_disk_size must be a number, got '$DISK_SIZE'"
exit 1
fi
echo "Setting HP masternode disk size to ${DISK_SIZE}GB..."
if [[ -n "$CURRENT_SIZE" ]]; then
sed -i "s/hpmn_node_disk_size = .*/hpmn_node_disk_size = $DISK_SIZE/" "$TFVARS_FILE"
else
echo "hpmn_node_disk_size = $DISK_SIZE" >> "$TFVARS_FILE"
fi
fi
echo "Terraform config:"
cat "$TFVARS_FILE"
- name: Deploy devnet (Terraform + Ansible)
env:
TF_IN_AUTOMATION: "true"
TF_CLI_ARGS_apply: "-auto-approve"
DEPLOY_TAGS: ${{ github.event.inputs.deploy_tags }}
run: |
echo "============================================"
echo "Deploying $NETWORK_NAME"
echo "Ansible tags: $DEPLOY_TAGS"
echo "============================================"
chmod +x ./bin/deploy
# GitHub Actions checks out a detached HEAD; bypass branch safety check.
if [[ "${{ steps.existing_configs.outputs.resume_mode }}" == "true" ]]; then
echo "Resume mode enabled. Skipping Terraform and re-running provisioning only."
./bin/deploy -p -f --tags="$DEPLOY_TAGS" "$NETWORK_NAME"
else
./bin/deploy -f --tags="$DEPLOY_TAGS" "$NETWORK_NAME"
fi
- name: Push configs to dash-network-configs
if: always()
env:
EVO_APP_DEPLOY_WRITE_KEY: ${{ secrets.EVO_APP_DEPLOY_WRITE_KEY }}
run: |
# Clone the configs repo to a temp directory
git clone git@github.com:dashpay/dash-network-configs.git /tmp/dash-network-configs
# Copy generated config files if present
COPIED=0
for ext in yml tfvars inventory; do
SRC="networks/$NETWORK_NAME.$ext"
if [[ -f "$SRC" ]]; then
cp "$SRC" /tmp/dash-network-configs/
COPIED=$((COPIED + 1))
else
echo "Skipping missing file: $SRC"
fi
done
if [[ $COPIED -eq 0 ]]; then
echo "No config files found to push"
exit 0
fi
# Commit and push
cd /tmp/dash-network-configs
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add .
git commit -m "Add configs for $NETWORK_NAME" || echo "No changes to commit"
# Use optional write key if configured; otherwise try default key.
if [[ -n "$EVO_APP_DEPLOY_WRITE_KEY" && -f "$HOME/.ssh/id_ed25519_write" ]]; then
GIT_SSH_COMMAND='ssh -i ~/.ssh/id_ed25519_write -o StrictHostKeyChecking=no' git push || {
echo "::warning::Failed to push configs with EVO_APP_DEPLOY_WRITE_KEY"
exit 0
}
else
git push || {
echo "::warning::Failed to push configs (likely read-only EVO_APP_DEPLOY_KEY). Configure secret EVO_APP_DEPLOY_WRITE_KEY with write access."
exit 0
}
fi
echo "Configs pushed to dash-network-configs repo"
- name: Verify platform services
if: success()
run: |
echo "Verifying platform services on HP masternodes..."
ansible hp_masternodes \
-i "networks/$NETWORK_NAME.inventory" \
--private-key="$HOME/.ssh/id_rsa" \
-b -m shell \
-a 'sudo -u dashmate dashmate status services --format=json | jq -r ".[] | select(.service != \"core\") | \"\(.service): \(.status)\""'
- name: Print summary
if: always()
run: |
echo "============================================"
echo "Devnet: $NETWORK_NAME"
echo "============================================"
echo ""
echo "To update this devnet later, use the 'Platform Version Deployment' workflow"
echo "To destroy this devnet, use the 'Destroy Devnet' workflow"