Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/build-ami.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Build DAppNode AMI

on:
workflow_run:
workflows: ["Release"]
types: [completed]

permissions:
id-token: write
contents: read

jobs:
build-ami:
name: Build DAppNode AMI
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.IMAGE_BUILDER_ROLE_ARN }}
aws-region: us-east-1

- name: Bump recipe and trigger AMI build
env:
PIPELINE_ARN: ${{ secrets.IMAGE_BUILDER_PIPELINE_ARN }}
INFRA_ARN: ${{ secrets.IMAGE_BUILDER_INFRA_ARN }}
DIST_ARN: ${{ secrets.IMAGE_BUILDER_DIST_ARN }}
run: |
# Get current recipe and extract component + version
CURRENT_RECIPE_ARN=$(aws imagebuilder get-image-pipeline \
--image-pipeline-arn "$PIPELINE_ARN" \
--query 'imagePipeline.imageRecipeArn' --output text)

CURRENT_VERSION=$(echo "$CURRENT_RECIPE_ARN" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+$')
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
echo "Recipe: $CURRENT_VERSION -> $NEW_VERSION"

# Get component from current recipe
COMPONENT_ARN=$(aws imagebuilder get-image-recipe \
--image-recipe-arn "$CURRENT_RECIPE_ARN" \
--query 'imageRecipe.components[0].componentArn' --output text)
echo "Component: $COMPONENT_ARN"

# Create new recipe version (same component, latest Ubuntu 24)
RECIPE_ARN=$(aws imagebuilder create-image-recipe \
--name "dappnode-image" \
--semantic-version "$NEW_VERSION" \
--parent-image "arn:aws:imagebuilder:us-east-1:aws:image/ubuntu-server-24-lts-x86/x.x.x" \
--components "[{\"componentArn\":\"$COMPONENT_ARN\"}]" \
--block-device-mappings '[{"deviceName":"/dev/sda1","ebs":{"volumeSize":16,"volumeType":"gp3","deleteOnTermination":true}}]' \
--working-directory "/tmp" \
--query 'imageRecipeArn' --output text)

# Update pipeline and trigger
aws imagebuilder update-image-pipeline \
--image-pipeline-arn "$PIPELINE_ARN" \
--image-recipe-arn "$RECIPE_ARN" \
--infrastructure-configuration-arn "$INFRA_ARN" \
--distribution-configuration-arn "$DIST_ARN" \
--image-tests-configuration "imageTestsEnabled=false"

EXECUTION=$(aws imagebuilder start-image-pipeline-execution \
--image-pipeline-arn "$PIPELINE_ARN" \
--query 'imageBuildVersionArn' --output text)

echo "### AMI Build Triggered 🚀" >> "$GITHUB_STEP_SUMMARY"
echo "- **Recipe:** ${NEW_VERSION}" >> "$GITHUB_STEP_SUMMARY"
echo "- **Image ARN:** ${EXECUTION}" >> "$GITHUB_STEP_SUMMARY"
7 changes: 4 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Pre-release
name: Release

on:
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -247,11 +248,11 @@ jobs:
run: |
echo "Images directory content:"
ls -lrt images/
- name: Create pre-release
- name: Create release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.set-versions.outputs.core }}
prerelease: true
prerelease: false
files: |
./images/Dappnode-*-debian-*-attended.iso
./images/Dappnode-*-debian-*-unattended.iso
Expand Down
125 changes: 125 additions & 0 deletions scripts/dappnode_ami_build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/bash
# DAppNode AMI Build Script
# Purpose: Install prerequisites, pre-download core Docker images, and set up
# first-boot installer for EC2 Image Builder.
#
# Env vars:
# PROFILE_URL — URL to dappnode_profile.sh (defaults to latest release)
#
# The installer still runs at first boot (via rc.local), but finds the heavy
# Docker images already cached in /usr/src/dappnode/DNCORE/, making boot fast.

set -euo pipefail

: "${PROFILE_URL:=https://github.com/dappnode/DAppNode/releases/latest/download/dappnode_profile.sh}"

DAPPNODE_DIR="/usr/src/dappnode"
DNCORE_DIR="$DAPPNODE_DIR/DNCORE"
LOGS_DIR="$DAPPNODE_DIR/logs"
LOG_FILE="$LOGS_DIR/ami_build.log"

export DEBIAN_FRONTEND=noninteractive

mkdir -p "$DAPPNODE_DIR/scripts" "$DNCORE_DIR" "$LOGS_DIR"
touch "$LOG_FILE"
exec > >(tee -a "$LOG_FILE") 2>&1

log() { echo "[AMI-BUILD] $*"; }

lsb_dist="$(. /etc/os-release && echo "$ID")"
log "OS: $lsb_dist | Profile: $PROFILE_URL"

# ─── Phase 1: Prerequisites ──────────────────────────────────────────────────
log "=== Phase 1: Prerequisites ==="

apt-get update -y

if ! docker -v >/dev/null 2>&1; then
log "Installing Docker..."
apt-get remove -y docker docker-engine docker.io containerd runc || true
apt-get install -y ca-certificates curl lsb-release
install -m 0755 -d /etc/apt/keyrings
curl -fsSL "https://download.docker.com/linux/${lsb_dist}/gpg" -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$lsb_dist $(lsb_release -cs) stable" \
| tee /etc/apt/sources.list.d/docker.list >/dev/null
apt-get update -y
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
fi

cat >/usr/local/bin/docker-compose <<'EOL'
#!/bin/bash
docker compose "$@"
EOL
chmod +x /usr/local/bin/docker-compose

modprobe wireguard 2>/dev/null || apt-get install -y wireguard-dkms || apt-get install -y wireguard-tools || true
apt-get install -y lsof iptables xz-utils || true

# ─── Phase 2: Pre-download core images ───────────────────────────────────────
log "=== Phase 2: Pre-downloading core images ==="

wget -O "$DNCORE_DIR/.dappnode_profile" "$PROFILE_URL"

# Source only the version variables (up to ISOBUILD marker)
sed '/^\#\!ISOBUILD/q' "$DNCORE_DIR/.dappnode_profile" > /tmp/vars.sh
source /tmp/vars.sh

COMPONENTS=(BIND IPFS WIREGUARD DAPPMANAGER WIFI HTTPS)

for comp in "${COMPONENTS[@]}"; do
ver="${comp}_VERSION"
comp_lower="$(echo "$comp" | tr '[:upper:]' '[:lower:]')"
VERSION="${!ver}"

if [[ "$VERSION" == /ipfs/* ]]; then
log "Skipping $comp (IPFS-based version)"
continue
fi

BASE_URL="https://github.com/dappnode/DNP_${comp}/releases/download/v${VERSION}"

log "Downloading $comp v${VERSION}..."
wget -q -O "$DNCORE_DIR/${comp_lower}.dnp.dappnode.eth_${VERSION}_linux-amd64.txz" \
"${BASE_URL}/${comp_lower}.dnp.dappnode.eth_${VERSION}_linux-amd64.txz" || \
log "WARNING: Failed to download $comp image"

wget -q -O "$DNCORE_DIR/docker-compose-${comp_lower}.yml" \
"${BASE_URL}/docker-compose.yml" || \
log "WARNING: Failed to download $comp compose"

wget -q -O "$DNCORE_DIR/dappnode_package-${comp_lower}.json" \
"${BASE_URL}/dappnode_package.json" || \
log "WARNING: Failed to download $comp manifest"
done

# Content hashes for execution/consensus clients
CONTENT_HASH_PKGS=(besu geth nethermind erigon prysm teku lighthouse lodestar nimbus)
HASH_FILE="$DNCORE_DIR/packages-content-hash.csv"
rm -f "$HASH_FILE"
for pkg in "${CONTENT_HASH_PKGS[@]}"; do
HASH=$(wget -q -O- "https://github.com/dappnode/DAppNodePackage-${pkg}/releases/latest/download/content-hash" || true)
if [ -n "$HASH" ]; then
echo "${pkg}.dnp.dappnode.eth,${HASH}" >> "$HASH_FILE"
log "Got content hash: $pkg"
fi
done

log "Pre-download complete:"
du -sh "$DNCORE_DIR/"

# ─── Phase 3: First-boot installer ───────────────────────────────────────────
log "=== Phase 3: First-boot setup ==="

wget -O "$DAPPNODE_DIR/scripts/dappnode_install.sh" https://installer.dappnode.io
chmod +x "$DAPPNODE_DIR/scripts/dappnode_install.sh"

cat > /etc/rc.local << 'RC'
#!/bin/sh -e
/usr/src/dappnode/scripts/dappnode_install.sh
exit 0
RC
chmod +x /etc/rc.local
touch "$DAPPNODE_DIR/.firstboot"

log "=== AMI build complete ==="
Loading