diff --git a/.github/workflows/create-devnet.yml b/.github/workflows/create-devnet.yml index 8c4652a5..c13a9985 100644 --- a/.github/workflows/create-devnet.yml +++ b/.github/workflows/create-devnet.yml @@ -17,7 +17,6 @@ on: 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 @@ -121,7 +120,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y python3-pip python3-netaddr sshpass jq + sudo apt-get install -y python3-pip python3-netaddr sshpass jq gnupg - name: Install Ansible run: | @@ -134,33 +133,21 @@ jobs: mkdir -p ~/.ansible/roles cp -r ansible/roles/* ~/.ansible/roles/ - - name: Set up SSH keys + - name: Set up GitHub 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 @@ -175,51 +162,106 @@ jobs: 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: | + mkdir -p networks 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 + for ext in yml tfvars inventory ssh.asc; 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 + if [[ $FOUND -eq 4 ]]; 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/ + cp "/tmp/dash-network-configs-source/$NETWORK_NAME.ssh.asc" 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 + ls -la "/tmp/dash-network-configs-source/$NETWORK_NAME"* 2>/dev/null || true + exit 1 + fi + + - name: Prepare devnet SSH key + env: + DEVNET_CI_GPG_PRIVATE_KEY: ${{ secrets.DEVNET_CI_GPG_PRIVATE_KEY }} + DEVNET_CI_GPG_PASSPHRASE: ${{ secrets.DEVNET_CI_GPG_PASSPHRASE }} + VIVEK_GPG_PUBLIC_KEY: ${{ secrets.VIVEK_GPG_PUBLIC_KEY }} + LATTE_GPG_PUBLIC_KEY: ${{ secrets.LATTE_GPG_PUBLIC_KEY }} + run: | + export GNUPGHOME="$RUNNER_TEMP/gnupg" + mkdir -p "$GNUPGHOME" ~/.ssh + chmod 700 "$GNUPGHOME" ~/.ssh + + printf '%s' "$DEVNET_CI_GPG_PRIVATE_KEY" > "$RUNNER_TEMP/devnet-ci-private.asc" + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --import "$RUNNER_TEMP/devnet-ci-private.asc" + + if [[ "${{ steps.existing_configs.outputs.resume_mode }}" == "true" ]]; then + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --decrypt --output "$HOME/.ssh/id_rsa" "networks/$NETWORK_NAME.ssh.asc" + chmod 600 "$HOME/.ssh/id_rsa" + ssh-keygen -y -f "$HOME/.ssh/id_rsa" > "$HOME/.ssh/id_rsa.pub" + chmod 644 "$HOME/.ssh/id_rsa.pub" + exit 0 + fi + + ssh-keygen -t rsa -b 4096 -N '' -C "$NETWORK_NAME" -f "$HOME/.ssh/id_rsa" + chmod 600 "$HOME/.ssh/id_rsa" + chmod 644 "$HOME/.ssh/id_rsa.pub" + + printf '%s' "$VIVEK_GPG_PUBLIC_KEY" > "$RUNNER_TEMP/vivek-public.asc" + printf '%s' "$LATTE_GPG_PUBLIC_KEY" > "$RUNNER_TEMP/latte-public.asc" + gpg --batch --yes --import "$RUNNER_TEMP/vivek-public.asc" + gpg --batch --yes --import "$RUNNER_TEMP/latte-public.asc" + + mapfile -t recipients < <( + gpg --batch --with-colons --list-keys | + awk -F: '$1 == "pub" {want=1; next} want && $1 == "fpr" {print $10; want=0}' + ) + + if [[ ${#recipients[@]} -lt 3 ]]; then + echo "Error: expected at least 3 GPG recipients, found ${#recipients[@]}" + gpg --batch --list-keys exit 1 fi + recipient_args=() + for recipient in "${recipients[@]}"; do + recipient_args+=(--recipient "$recipient") + done + + gpg --batch --yes --trust-model always --armor --encrypt \ + "${recipient_args[@]}" \ + --output "networks/$NETWORK_NAME.ssh.asc" \ + "$HOME/.ssh/id_rsa" + + - name: Create networks/.env + run: | + mkdir -p networks + cat > networks/.env << EOF2 + 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 + EOF2 + - name: Generate network configs if: steps.existing_configs.outputs.resume_mode != 'true' env: @@ -229,7 +271,6 @@ jobs: 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 @@ -253,29 +294,20 @@ jobs: 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" @@ -290,16 +322,13 @@ jobs: 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" + sed -i "s|^main_domain\s*=.*|main_domain = \"$DEFAULT_MAIN_DOMAIN\"|" "$TFVARS_FILE" else echo "main_domain = \"$DEFAULT_MAIN_DOMAIN\"" >> "$TFVARS_FILE" fi @@ -333,7 +362,6 @@ jobs: 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" @@ -346,12 +374,10 @@ jobs: 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 + for ext in yml tfvars inventory ssh.asc; do SRC="networks/$NETWORK_NAME.$ext" if [[ -f "$SRC" ]]; then cp "$SRC" /tmp/dash-network-configs/ @@ -366,14 +392,17 @@ jobs: exit 0 fi - # Commit and push + if [[ $COPIED -ne 4 ]]; then + echo "::warning::Not pushing partial devnet config set for $NETWORK_NAME" + exit 0 + fi + 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" @@ -405,5 +434,6 @@ jobs: echo "Devnet: $NETWORK_NAME" echo "============================================" echo "" + echo "Encrypted SSH key stored as: $NETWORK_NAME.ssh.asc in dash-network-configs" echo "To update this devnet later, use the 'Platform Version Deployment' workflow" echo "To destroy this devnet, use the 'Destroy Devnet' workflow" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 946d0d4c..3ee84ca8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,10 @@ on: jobs: deploy: name: Deploy Dash Network - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + + env: + ANSIBLE_HOST_KEY_CHECKING: "false" steps: - name: Checkout dash-network-deploy @@ -30,88 +33,112 @@ jobs: with: node-version: '20' + - name: Install Node.js dependencies + run: npm ci + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3-pip python3-netaddr sshpass jq gnupg + - name: Install Ansible run: | - python -m pip install --upgrade pip - pip install ansible + python3 -m pip install --upgrade pip + python3 -m pip install ansible-core==2.16.3 jmespath - # Setup SSH keys - 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 + - name: Set up GitHub SSH key + env: + EVO_APP_DEPLOY_KEY: ${{ secrets.EVO_APP_DEPLOY_KEY }} run: | mkdir -p ~/.ssh - - # GitHub deploy key for cloning configs - echo "${{ secrets.EVO_APP_DEPLOY_KEY }}" > ~/.ssh/id_ed25519 + printf '%s\n' "$EVO_APP_DEPLOY_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 - # Server SSH key for connecting to nodes - echo "${{ secrets.DEPLOY_SERVER_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - - # 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 - # Set up Node.js and clone configs - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - name: Install dependencies - run: npm ci - - # Clone network configs - name: Clone network configs run: | rm -rf networks git clone git@github.com:dashpay/dash-network-configs.git networks - # Verify network config exists - name: Check network config + env: + NETWORK: ${{ github.event.inputs.network_name }} run: | - if [ ! -f "networks/${{ github.event.inputs.network_name }}.yml" ]; then - echo "Error: Network config networks/${{ github.event.inputs.network_name }}.yml not found" + if [[ ! -f "networks/$NETWORK.yml" ]]; then + echo "Error: Network config networks/$NETWORK.yml not found" exit 1 fi - # Install Ansible and Python dependencies - - name: Install Ansible and Dependencies + if [[ "$NETWORK" == devnet-* && ! -f "networks/$NETWORK.ssh.asc" ]]; then + echo "Error: Encrypted devnet SSH key networks/$NETWORK.ssh.asc not found" + exit 1 + fi + + - name: Prepare server SSH key + env: + NETWORK: ${{ github.event.inputs.network_name }} + DEPLOY_SERVER_KEY: ${{ secrets.DEPLOY_SERVER_KEY }} + DEVNET_CI_GPG_PRIVATE_KEY: ${{ secrets.DEVNET_CI_GPG_PRIVATE_KEY }} + DEVNET_CI_GPG_PASSPHRASE: ${{ secrets.DEVNET_CI_GPG_PASSPHRASE }} run: | - sudo apt-get update - sudo apt-get install -y python3-pip python3-netaddr sshpass - pip3 install ansible + export GNUPGHOME="$RUNNER_TEMP/gnupg" + mkdir -p "$GNUPGHOME" ~/.ssh + chmod 700 "$GNUPGHOME" ~/.ssh + + if [[ "$NETWORK" == devnet-* ]]; then + printf '%s' "$DEVNET_CI_GPG_PRIVATE_KEY" > "$RUNNER_TEMP/devnet-ci-private.asc" + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --import "$RUNNER_TEMP/devnet-ci-private.asc" + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --decrypt --output "$HOME/.ssh/id_rsa" "networks/$NETWORK.ssh.asc" + else + printf '%s\n' "$DEPLOY_SERVER_KEY" > "$HOME/.ssh/id_rsa" + fi + + chmod 600 "$HOME/.ssh/id_rsa" + ssh-keygen -y -f "$HOME/.ssh/id_rsa" > "$HOME/.ssh/id_rsa.pub" + chmod 644 "$HOME/.ssh/id_rsa.pub" + + 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 - # Install required Ansible roles from Galaxy and local roles - - name: Install Ansible Roles + - name: Create networks/.env run: | - mkdir -p ~/.ansible/roles - cp -r ansible/roles/* ~/.ansible/roles/ + cat > networks/.env << EOF2 + PRIVATE_KEY_PATH=$HOME/.ssh/id_rsa + PUBLIC_KEY_PATH=$HOME/.ssh/id_rsa.pub + EOF2 - # Run deploy - name: Run Deploy Script env: - ANSIBLE_CONFIG: ansible.cfg NETWORK: ${{ github.event.inputs.network_name }} NETWORK_PATH: networks/${{ github.event.inputs.network_name }}.yml - ANSIBLE_HOST_KEY_CHECKING: "false" run: | - pwd - ls -la - ls -la networks/ chmod +x ./bin/github-deploy - ./bin/github-deploy -p ${{ github.event.inputs.network_name }} --tags ${{ github.event.inputs.deploy_tag }} + ./bin/github-deploy -p "$NETWORK" --tags "${{ github.event.inputs.deploy_tag }}" diff --git a/.github/workflows/destroy-devnet.yml b/.github/workflows/destroy-devnet.yml index 3ba4f86f..3e1735c6 100644 --- a/.github/workflows/destroy-devnet.yml +++ b/.github/workflows/destroy-devnet.yml @@ -85,7 +85,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y python3-pip python3-netaddr sshpass jq + sudo apt-get install -y python3-pip python3-netaddr sshpass jq gnupg - name: Install Ansible run: | @@ -98,33 +98,21 @@ jobs: mkdir -p ~/.ansible/roles cp -r ansible/roles/* ~/.ansible/roles/ - - name: Set up SSH keys + - name: Set up GitHub 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 @@ -139,34 +127,21 @@ jobs: 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: Clone network configs run: | + mkdir -p networks rm -rf networks/.git git clone git@github.com:dashpay/dash-network-configs.git /tmp/dash-network-configs - # Copy config files for this devnet cp /tmp/dash-network-configs/$NETWORK_NAME.yml networks/ 2>/dev/null || true cp /tmp/dash-network-configs/$NETWORK_NAME.tfvars networks/ 2>/dev/null || true cp /tmp/dash-network-configs/$NETWORK_NAME.inventory networks/ 2>/dev/null || true + cp /tmp/dash-network-configs/$NETWORK_NAME.ssh.asc networks/ 2>/dev/null || true - name: Validate config files exist run: | MISSING=() - for ext in yml tfvars inventory; do + for ext in yml tfvars inventory ssh.asc; do if [[ ! -f "networks/$NETWORK_NAME.$ext" ]]; then MISSING+=("networks/$NETWORK_NAME.$ext") fi @@ -186,6 +161,42 @@ jobs: echo "Found all config files for $NETWORK_NAME" ls -la networks/$NETWORK_NAME.* + - name: Prepare devnet SSH key + env: + DEVNET_CI_GPG_PRIVATE_KEY: ${{ secrets.DEVNET_CI_GPG_PRIVATE_KEY }} + DEVNET_CI_GPG_PASSPHRASE: ${{ secrets.DEVNET_CI_GPG_PASSPHRASE }} + run: | + export GNUPGHOME="$RUNNER_TEMP/gnupg" + mkdir -p "$GNUPGHOME" ~/.ssh + chmod 700 "$GNUPGHOME" ~/.ssh + + printf '%s' "$DEVNET_CI_GPG_PRIVATE_KEY" > "$RUNNER_TEMP/devnet-ci-private.asc" + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --import "$RUNNER_TEMP/devnet-ci-private.asc" + + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --decrypt --output "$HOME/.ssh/id_rsa" "networks/$NETWORK_NAME.ssh.asc" + + chmod 600 "$HOME/.ssh/id_rsa" + ssh-keygen -y -f "$HOME/.ssh/id_rsa" > "$HOME/.ssh/id_rsa.pub" + chmod 644 "$HOME/.ssh/id_rsa.pub" + + - name: Create networks/.env + run: | + mkdir -p networks + cat > networks/.env << EOF2 + 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 + EOF2 + - name: Print destruction plan run: | echo "============================================" @@ -226,14 +237,13 @@ jobs: git config user.name "GitHub Actions" git config user.email "actions@github.com" - # Remove config files for this devnet git rm "$NETWORK_NAME.yml" 2>/dev/null || true git rm "$NETWORK_NAME.tfvars" 2>/dev/null || true git rm "$NETWORK_NAME.inventory" 2>/dev/null || true + git rm "$NETWORK_NAME.ssh.asc" 2>/dev/null || true git commit -m "Remove configs for $NETWORK_NAME (destroyed)" || 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 config removal with EVO_APP_DEPLOY_WRITE_KEY" @@ -241,7 +251,8 @@ jobs: } else git push || { - echo "::warning::Failed to push config removal (likely read-only EVO_APP_DEPLOY_KEY). Configure secret EVO_APP_DEPLOY_WRITE_KEY with write access." + echo "::warning::Failed to push config removal." + echo "::warning::Configure EVO_APP_DEPLOY_WRITE_KEY with write access." exit 0 } fi diff --git a/.github/workflows/platform-deploy.yml b/.github/workflows/platform-deploy.yml index 6ec85126..ca4eac3a 100644 --- a/.github/workflows/platform-deploy.yml +++ b/.github/workflows/platform-deploy.yml @@ -22,7 +22,10 @@ on: jobs: deploy: name: Deploy Platform Version - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + + env: + ANSIBLE_HOST_KEY_CHECKING: "false" steps: - name: Checkout dash-network-deploy @@ -33,129 +36,153 @@ jobs: with: node-version: '20' + - name: Install Node.js dependencies + run: npm ci + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3-pip python3-netaddr sshpass jq gnupg + - name: Install Ansible run: | - python -m pip install --upgrade pip - pip install ansible + python3 -m pip install --upgrade pip + python3 -m pip install ansible-core==2.16.3 jmespath - # Setup SSH keys - 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 + - name: Set up GitHub SSH key + env: + EVO_APP_DEPLOY_KEY: ${{ secrets.EVO_APP_DEPLOY_KEY }} run: | mkdir -p ~/.ssh - - # GitHub deploy key for cloning configs - echo "${{ secrets.EVO_APP_DEPLOY_KEY }}" > ~/.ssh/id_ed25519 + printf '%s\n' "$EVO_APP_DEPLOY_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 - # Server SSH key for connecting to nodes - echo "${{ secrets.DEPLOY_SERVER_KEY }}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - - # 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 - # Set up Node.js and clone configs - - name: Install dependencies - run: npm ci + chmod 600 ~/.ssh/config - # Clone network configs - name: Clone network configs run: | rm -rf networks git clone git@github.com:dashpay/dash-network-configs.git networks - # Verify network config exists - name: Check network config + env: + NETWORK: ${{ github.event.inputs.network }} run: | - if [ ! -f "networks/${{ github.event.inputs.network }}.yml" ]; then - echo "Error: Network config networks/${{ github.event.inputs.network }}.yml not found" + if [[ ! -f "networks/$NETWORK.yml" ]]; then + echo "Error: Network config networks/$NETWORK.yml not found" + exit 1 + fi + + if [[ "$NETWORK" == devnet-* && ! -f "networks/$NETWORK.ssh.asc" ]]; then + echo "Error: Encrypted devnet SSH key networks/$NETWORK.ssh.asc not found" exit 1 fi - # Install Ansible and Python dependencies - - name: Install Ansible and Dependencies + - name: Prepare server SSH key + env: + NETWORK: ${{ github.event.inputs.network }} + DEPLOY_SERVER_KEY: ${{ secrets.DEPLOY_SERVER_KEY }} + DEVNET_CI_GPG_PRIVATE_KEY: ${{ secrets.DEVNET_CI_GPG_PRIVATE_KEY }} + DEVNET_CI_GPG_PASSPHRASE: ${{ secrets.DEVNET_CI_GPG_PASSPHRASE }} run: | - sudo apt-get update - sudo apt-get install -y python3-pip python3-netaddr sshpass - pip3 install ansible + export GNUPGHOME="$RUNNER_TEMP/gnupg" + mkdir -p "$GNUPGHOME" ~/.ssh + chmod 700 "$GNUPGHOME" ~/.ssh + + if [[ "$NETWORK" == devnet-* ]]; then + printf '%s' "$DEVNET_CI_GPG_PRIVATE_KEY" > "$RUNNER_TEMP/devnet-ci-private.asc" + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --import "$RUNNER_TEMP/devnet-ci-private.asc" + gpg --batch --yes --pinentry-mode loopback \ + --passphrase "$DEVNET_CI_GPG_PASSPHRASE" \ + --decrypt --output "$HOME/.ssh/id_rsa" "networks/$NETWORK.ssh.asc" + else + printf '%s\n' "$DEPLOY_SERVER_KEY" > "$HOME/.ssh/id_rsa" + fi + + chmod 600 "$HOME/.ssh/id_rsa" + ssh-keygen -y -f "$HOME/.ssh/id_rsa" > "$HOME/.ssh/id_rsa.pub" + chmod 644 "$HOME/.ssh/id_rsa.pub" + + cat > ~/.ssh/config << 'EOL' + Host github.com + IdentityFile ~/.ssh/id_ed25519 + StrictHostKeyChecking no - # Install required Ansible roles from Galaxy and local roles - - name: Install Ansible Roles + Host * + IdentityFile ~/.ssh/id_rsa + User ubuntu + StrictHostKeyChecking no + UserKnownHostsFile=/dev/null + EOL + + chmod 600 ~/.ssh/config + + - name: Create networks/.env run: | - mkdir -p ~/.ansible/roles - cp -r ansible/roles/* ~/.ansible/roles/ - # Update platform version in network config + cat > networks/.env << EOF2 + PRIVATE_KEY_PATH=$HOME/.ssh/id_rsa + PUBLIC_KEY_PATH=$HOME/.ssh/id_rsa.pub + EOF2 + - name: Update platform version in network config + env: + NETWORK: ${{ github.event.inputs.network }} + VERSION: ${{ github.event.inputs.platform_version }} run: | - # Update dashmate_version - sed -i "s/dashmate_version: .*/dashmate_version: ${{ github.event.inputs.platform_version }}/" networks/${{ github.event.inputs.network }}.yml - - # Update platform service image versions - sed -i "s/drive_image: dashpay\/drive:[^ ]*/drive_image: dashpay\/drive:${{ github.event.inputs.platform_version }}/" networks/${{ github.event.inputs.network }}.yml - sed -i "s/dapi_image: dashpay\/dapi:[^ ]*/dapi_image: dashpay\/dapi:${{ github.event.inputs.platform_version }}/" networks/${{ github.event.inputs.network }}.yml - - # Update rs_dapi_image (group_vars/all defines it without a tag, so we must - # explicitly set it to get the right version instead of :latest) - CONFIG_FILE="networks/${{ github.event.inputs.network }}.yml" + CONFIG_FILE="networks/$NETWORK.yml" + SAFE_VERSION=$(printf '%s' "$VERSION" | sed 's/[&\\/]/\\&/g') + + sed -i "s/dashmate_version: .*/dashmate_version: $SAFE_VERSION/" "$CONFIG_FILE" + 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" + if grep -q "rs_dapi_image:" "$CONFIG_FILE"; then - sed -i "s|rs_dapi_image: dashpay/rs-dapi:[^ ]*|rs_dapi_image: dashpay/rs-dapi:${{ github.event.inputs.platform_version }}|" "$CONFIG_FILE" + sed -i "s|rs_dapi_image: dashpay/rs-dapi:[^ ]*|rs_dapi_image: dashpay/rs-dapi:$SAFE_VERSION|" "$CONFIG_FILE" else - echo "rs_dapi_image: dashpay/rs-dapi:${{ github.event.inputs.platform_version }}" >> "$CONFIG_FILE" + echo "rs_dapi_image: dashpay/rs-dapi:$VERSION" >> "$CONFIG_FILE" fi echo "Updated network config:" - grep -E "(dashmate_version|drive_image|dapi_image|rs_dapi_image)" networks/${{ github.event.inputs.network }}.yml + grep -E "(dashmate_version|drive_image|dapi_image|rs_dapi_image)" "$CONFIG_FILE" - # Run platform deployment - name: Run Platform Deployment env: ANSIBLE_CONFIG: ansible.cfg NETWORK: ${{ github.event.inputs.network }} NETWORK_PATH: networks/${{ github.event.inputs.network }}.yml - ANSIBLE_HOST_KEY_CHECKING: "false" run: | - pwd - ls -la - ls -la networks/ chmod +x ./bin/deploy - - # Build deployment command + DEPLOY_CMD="./bin/deploy -p" - - # Add fast mode flag if enabled - if [ "${{ github.event.inputs.fast_mode }}" == "true" ]; then + if [[ "${{ github.event.inputs.fast_mode }}" == "true" ]]; then DEPLOY_CMD="$DEPLOY_CMD --fast" fi - - # Add dashmate deployment tag - DEPLOY_CMD="$DEPLOY_CMD --tags=dashmate_deploy" - - # Add network - DEPLOY_CMD="$DEPLOY_CMD ${{ github.event.inputs.network }}" - + DEPLOY_CMD="$DEPLOY_CMD --tags=dashmate_deploy $NETWORK" + echo "Running: $DEPLOY_CMD" $DEPLOY_CMD - # Verify deployment - name: Verify Platform Services env: - ANSIBLE_HOST_KEY_CHECKING: "false" + NETWORK: ${{ github.event.inputs.network }} run: | echo "Verifying platform services are running..." - ansible hp_masternodes -i "networks/${{ github.event.inputs.network }}.inventory" -b -m shell -a 'sudo -u dashmate dashmate status services --format=json | jq -r ".[] | select(.service != \"core\") | \"\(.service): \(.status)\""' || true + ansible hp_masternodes \ + -i "networks/$NETWORK.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)\""' || true diff --git a/ansible/roles/dashmate/tasks/main.yml b/ansible/roles/dashmate/tasks/main.yml index 83f67303..61a0333c 100644 --- a/ansible/roles/dashmate/tasks/main.yml +++ b/ansible/roles/dashmate/tasks/main.yml @@ -133,7 +133,6 @@ append: true when: - not (skip_dashmate_image_update | default(false)) - - not dashmate_user_exists # ============================================================================ # EARLY PERMISSION FIXES - Ensure all dashmate directories have correct ownership @@ -255,9 +254,14 @@ - dashmate_version is defined - (dashmate_needs_update | default(true)) or force_dashmate_reinstall | default(false) -- name: Set dashmate config version from already extracted version +- name: Set dashmate config version ansible.builtin.set_fact: - dashmate_config_version: "{{ installed_dashmate_version }}" + dashmate_config_version: >- + {{ + dashmate_version + if dashmate_version is defined + else installed_dashmate_version + }} # This check will return an error code if config is missing, config schema is invalid or default config is not yet set - name: Check state of dashmate config