diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..223d0b6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +target/ +.git/ +.github/ +.claude/ +.worktrees/ +.config/ +local-test/ +tests/ +*.md +deny.toml +typos.toml +.editorconfig +.gitignore diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..6cd021a --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,56 @@ +name: Docker + +on: + push: + tags: ["v*"] + workflow_dispatch: + +permissions: + contents: read + packages: write + +env: + IMAGE_NAME: ghcr.io/morph-l2/morph-reth + +jobs: + build-push: + name: Build and Push + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix= + type=raw,value=latest,enable=${{ !contains(github.ref, '-') }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }} + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9f3a1ba --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,174 @@ +name: Release + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + tag: + description: "Tag to release (e.g., v0.1.0)" + required: true + dry_run: + description: "Dry run (skip release creation)" + type: boolean + default: true + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + extract-version: + name: Extract Version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + ref: ${{ steps.version.outputs.ref }} + steps: + - name: Determine tag + id: version + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + TAG="${{ inputs.tag }}" + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then + echo "::error::Invalid tag format: '$TAG'. Expected format like v0.1.0 or v0.1.0-rc.1" + exit 1 + fi + REF="refs/tags/${TAG}" + else + TAG="${GITHUB_REF#refs/tags/}" + REF="${GITHUB_REF}" + fi + VERSION="${TAG#v}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "ref=$REF" >> "$GITHUB_OUTPUT" + echo "Extracted version: $VERSION" + + check-version: + name: Check Version + runs-on: ubuntu-latest + needs: extract-version + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ needs.extract-version.outputs.ref }} + persist-credentials: false + + - name: Verify Cargo.toml version matches tag + run: | + CARGO_VERSION=$(cargo metadata --format-version 1 --no-deps \ + | jq -r '.packages[] | select(.name == "morph-reth") | .version') + TAG_VERSION="${{ needs.extract-version.outputs.version }}" + echo "Cargo.toml version: $CARGO_VERSION" + echo "Tag version: $TAG_VERSION" + if [[ "$TAG_VERSION" != "$CARGO_VERSION"* ]]; then + echo "::error::Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)" + exit 1 + fi + + build: + name: Build ${{ matrix.target }} + runs-on: ubuntu-latest + needs: [extract-version, check-version] + if: ${{ !cancelled() && needs.check-version.result == 'success' }} + strategy: + matrix: + include: + - target: x86_64-unknown-linux-gnu + archive: morph-reth-${{ needs.extract-version.outputs.version }}-x86_64-linux + - target: aarch64-unknown-linux-gnu + archive: morph-reth-${{ needs.extract-version.outputs.version }}-aarch64-linux + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ needs.extract-version.outputs.ref }} + persist-credentials: false + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Cache Rust build artifacts + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + key: ${{ matrix.target }} + + - name: Install cross + uses: taiki-e/install-action@v2 + with: + tool: cross + + - name: Build binary + run: cross build --release --locked --target ${{ matrix.target }} --bin morph-reth + + - name: Package binary + run: | + mkdir -p dist + cp target/${{ matrix.target }}/release/morph-reth dist/ + cd dist + tar czf ../${{ matrix.archive }}.tar.gz morph-reth + cd .. + sha256sum ${{ matrix.archive }}.tar.gz > ${{ matrix.archive }}.tar.gz.sha256 + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.archive }} + path: | + ${{ matrix.archive }}.tar.gz + ${{ matrix.archive }}.tar.gz.sha256 + + draft-release: + name: Draft Release + runs-on: ubuntu-latest + needs: [extract-version, build] + if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }} + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ needs.extract-version.outputs.ref }} + fetch-depth: 0 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: artifacts + + - name: Generate changelog + id: changelog + run: | + PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + if [[ -n "$PREVIOUS_TAG" ]]; then + CHANGELOG=$(git log --pretty=format:"- %s" ${PREVIOUS_TAG}..HEAD) + else + CHANGELOG=$(git log -n 20 --pretty=format:"- %s") + fi + # Write to file to avoid escaping issues + echo "$CHANGELOG" > changelog.md + + - name: Create draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ needs.extract-version.outputs.version }}" + PRERELEASE="" + if [[ "$VERSION" == *"alpha"* ]] || [[ "$VERSION" == *"beta"* ]] || [[ "$VERSION" == *"rc"* ]]; then + PRERELEASE="--prerelease" + fi + gh release create "v${VERSION}" \ + --verify-tag \ + --draft \ + --title "v${VERSION}" \ + --notes-file changelog.md \ + $PRERELEASE \ + artifacts/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..856f8bb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef +WORKDIR /app + +# reth-mdbx-sys requires libclang for bindgen +RUN apt-get update && \ + apt-get install -y --no-install-recommends libclang-dev pkg-config && \ + rm -rf /var/lib/apt/lists/* + +# Generate dependency recipe +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +# Build dependencies + application +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json + +# Build profile, release by default +ARG BUILD_PROFILE=release +ENV BUILD_PROFILE=$BUILD_PROFILE + +# Extra Cargo flags +ARG RUSTFLAGS="" +ENV RUSTFLAGS="$RUSTFLAGS" + +# Build dependencies (cached layer) +RUN cargo chef cook --profile $BUILD_PROFILE --recipe-path recipe.json + +# Build the application +COPY . . +RUN cargo build --profile $BUILD_PROFILE --locked --bin morph-reth + +# Copy binary to a fixed location (ARG not resolved in COPY) +RUN cp /app/target/$BUILD_PROFILE/morph-reth /app/morph-reth + +# Minimal runtime image +FROM debian:bookworm-slim AS runtime + +LABEL org.opencontainers.image.source=https://github.com/morph-l2/morph-reth +LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0" +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && \ + useradd --system --create-home --home-dir /var/lib/morph-reth --shell /usr/sbin/nologin morph-reth && \ + rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/morph-reth /usr/local/bin/ + +EXPOSE 8545 8546 8551 30303 30303/udp + +WORKDIR /var/lib/morph-reth +USER morph-reth +ENTRYPOINT ["/usr/local/bin/morph-reth"]