diff --git a/.dockerignore b/.dockerignore index f54659b9e1..bc16b22495 100644 --- a/.dockerignore +++ b/.dockerignore @@ -31,9 +31,6 @@ tools/heartbeats-processor/*.db # Ensure .sqlx files are included !tools/heartbeats-processor/.sqlx/ -# Will be copied on demand in the Docker image, if necessary -circuit-blobs - # GH workflows .claude .github @@ -65,6 +62,11 @@ tests.tsv # The website must not be included website +## Ignore some scripts that can be used in the Docker images. +## These scripts are used to be consistent with what is on the website and what +## is actually used. +!./website/docs/developers/scripts/frontend/install-nodejs-linux.sh +!./website/docs/developers/scripts/frontend/install-angular-cli.sh # NPM related node_modules @@ -75,4 +77,4 @@ frontend/.gitignore frontend/.vscode frontend/dist/frontend frontend/Dockerfile -frontend/node_modules +frontend/node_modules \ No newline at end of file diff --git a/.github/actions/frontend-build/action.yml b/.github/actions/frontend-build/action.yml index ed39f46dc9..b02be0d4ec 100644 --- a/.github/actions/frontend-build/action.yml +++ b/.github/actions/frontend-build/action.yml @@ -48,7 +48,7 @@ runs: working-directory: frontend shell: bash - # Test Makefile targets + # Test Makefile targets (alphabetical order) - name: Test make build if: inputs.test-build-commands == 'true' run: | @@ -58,13 +58,12 @@ runs: working-directory: frontend shell: bash - - - name: Test make build-development + - name: Test make build-block-producers if: inputs.test-build-commands == 'true' run: | rm -rf dist - make build-development - [ -d "dist/frontend" ] || { echo "Error: make build-development failed"; exit 1; } + make build-block-producers + [ -d "dist/frontend" ] || { echo "Error: make build-block-producers failed"; exit 1; } working-directory: frontend shell: bash @@ -77,24 +76,25 @@ runs: working-directory: frontend shell: bash - - name: Test make build-local + - name: Test make build-leaderboard if: inputs.test-build-commands == 'true' run: | rm -rf dist - make build-local - [ -d "dist/frontend" ] || { echo "Error: make build-local failed"; exit 1; } + make build-leaderboard + [ -d "dist/frontend" ] || { echo "Error: make build-leaderboard failed"; exit 1; } working-directory: frontend shell: bash - - name: Test make build-prod + - name: Test make build-local if: inputs.test-build-commands == 'true' run: | rm -rf dist - make build-prod - [ -d "dist/frontend" ] || { echo "Error: make build-prod failed"; exit 1; } + make build-local + [ -d "dist/frontend" ] || { echo "Error: make build-local failed"; exit 1; } working-directory: frontend shell: bash + - name: Test make build-producer if: inputs.test-build-commands == 'true' run: | @@ -113,15 +113,25 @@ runs: working-directory: frontend shell: bash - - name: Test make build-webnodelocal + - name: Test make build-staging if: inputs.test-build-commands == 'true' run: | rm -rf dist - make build-webnodelocal - [ -d "dist/frontend" ] || { echo "Error: make build-webnodelocal failed"; exit 1; } + make build-staging + [ -d "dist/frontend" ] || { echo "Error: make build-staging failed"; exit 1; } working-directory: frontend shell: bash + - name: Test make build-webnode + if: inputs.test-build-commands == 'true' + run: | + rm -rf dist + make build-webnode + [ -d "dist/frontend" ] || { echo "Error: make build-webnode failed"; exit 1; } + working-directory: frontend + shell: bash + + - name: Run tests if: inputs.run-tests == 'true' uses: cypress-io/github-action@v6 diff --git a/.github/scripts/docker/test-frontend-docker.sh b/.github/scripts/docker/test-frontend-docker.sh new file mode 100755 index 0000000000..53c97a0298 --- /dev/null +++ b/.github/scripts/docker/test-frontend-docker.sh @@ -0,0 +1,119 @@ +#!/bin/bash +# Test frontend Docker image with specific environment configuration +# Usage: ./test-frontend-docker.sh [port] +# +# Parameters: +# image - Docker image name/tag to test +# environment - Frontend environment configuration (local, webnode, production, development, producer, fuzzing) +# port - Optional port to use (default: 8080) +# +# Examples: +# ./test-frontend-docker.sh o1labs/mina-rust-frontend:latest production +# ./test-frontend-docker.sh test-frontend:local local 9090 + +set -euo pipefail + +# Check arguments +if [ $# -lt 2 ]; then + echo "Usage: $0 [port]" + echo "" + echo "Supported environments: local, webnode, production, development, producer, fuzzing" + echo "" + echo "Examples:" + echo " $0 o1labs/mina-rust-frontend:latest production" + echo " $0 test-frontend:local local 9090" + exit 1 +fi + +IMAGE="$1" +ENVIRONMENT="$2" +PORT="${3:-8080}" +CONTAINER_NAME="test-frontend-${ENVIRONMENT}-$$" + +# Supported environments +SUPPORTED_ENVS="local webnode production development producer fuzzing" +if [[ ! " $SUPPORTED_ENVS " =~ \ $ENVIRONMENT\ ]]; then + echo "โŒ Unsupported environment: $ENVIRONMENT" + echo "Supported environments: $SUPPORTED_ENVS" + exit 1 +fi + +echo "๐Ÿงช Testing frontend image with environment: $ENVIRONMENT" +echo "๐Ÿ“ฆ Image: $IMAGE" +echo "๐Ÿ”Œ Port: $PORT" +echo "๐Ÿ“ Container: $CONTAINER_NAME" +echo "" + +# Cleanup function +cleanup() { + echo "๐Ÿงน Cleaning up container: $CONTAINER_NAME" + docker stop "$CONTAINER_NAME" 2>/dev/null || true + docker rm "$CONTAINER_NAME" 2>/dev/null || true +} + +# Set up cleanup trap +trap cleanup EXIT + +# Check if port is available +if ss -tuln | grep -q ":$PORT "; then + echo "โŒ Port $PORT is already in use" + exit 1 +fi + +# Run the container with the specific environment configuration +echo "๐Ÿš€ Starting container..." +docker run --rm -d \ + --name "$CONTAINER_NAME" \ + -p "$PORT:80" \ + -e MINA_FRONTEND_ENVIRONMENT="$ENVIRONMENT" \ + "$IMAGE" + +# Wait a moment for container to start +echo "โณ Waiting for container to initialize..." +sleep 10 + +# Check if container is running +if ! docker ps | grep -q "$CONTAINER_NAME"; then + echo "โŒ Container failed to start with environment: $ENVIRONMENT" + echo "๐Ÿ“‹ Container logs:" + docker logs "$CONTAINER_NAME" || echo "No logs available" + exit 1 +fi + +echo "โœ… Container started successfully with environment: $ENVIRONMENT" + +# Test HTTP endpoint with retries (30 attempts with 3 second intervals = ~90s total) +RETRY_COUNT=0 +MAX_RETRIES=30 +SUCCESS=false + +echo "๐Ÿ” Testing HTTP endpoint with retries (max $MAX_RETRIES attempts)..." + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + if curl -f -s -m 5 "http://localhost:$PORT/" > /dev/null 2>&1; then + echo "โœ… HTTP endpoint is responding for environment: $ENVIRONMENT (attempt $((RETRY_COUNT + 1)))" + SUCCESS=true + break + else + RETRY_COUNT=$((RETRY_COUNT + 1)) + if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then + echo "โŒ HTTP endpoint not ready after $MAX_RETRIES attempts" + else + echo "โณ HTTP endpoint not ready, attempt $RETRY_COUNT/$MAX_RETRIES for environment: $ENVIRONMENT" + sleep 3 + fi + fi +done + +if [ "$SUCCESS" = false ]; then + echo "โŒ HTTP endpoint failed after $MAX_RETRIES attempts for environment: $ENVIRONMENT" + echo "๐Ÿ“‹ Container logs:" + docker logs "$CONTAINER_NAME" + exit 1 +fi + +echo "๐ŸŽ‰ Test completed successfully for environment: $ENVIRONMENT" +echo "" +echo "๐ŸŒ Frontend is available at: http://localhost:$PORT/" +echo "๐Ÿ” To view logs: docker logs $CONTAINER_NAME" +echo "๐Ÿ›‘ Container will be automatically stopped when script exits" \ No newline at end of file diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index b5e9196caa..7d12d9c071 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -75,8 +75,6 @@ jobs: runs-on: ubuntu-latest - platform: linux/arm64 runs-on: ubuntu-24.04-arm - configuration: - - build_configuration: production runs-on: ${{ matrix.arch.runs-on }} steps: - name: Prepare @@ -96,14 +94,20 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Download circuits files + uses: ./.github/actions/setup-circuits + + - name: Generate .env.docker + run: | + bash ./frontend/docker/generate-docker-env.sh + - name: Build Docker image id: build uses: docker/build-push-action@v6 with: - context: ./frontend + context: ./ + file: ./frontend/Dockerfile platforms: ${{ matrix.arch.platform }} - build-args: | - BUILD_CONFIGURATION=${{ matrix.configuration.build_configuration }} cache-from: type=gha cache-to: type=gha,mode=max outputs: type=image,name=${{ env.REGISTRY_FRONTEND_IMAGE }},push-by-digest=true,name-canonical=true,push=true @@ -117,17 +121,64 @@ jobs: - name: Upload digest uses: actions/upload-artifact@v4 with: - name: frontend-${{ matrix.configuration.build_configuration }}-digests-${{ env.PLATFORM_PAIR }} + name: frontend-digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 + # Test frontend image with all environment configurations + test-frontend-image: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/release') + needs: + - build-mina-frontend-image + strategy: + matrix: + environment: [local, webnode, production, development, producer, fuzzing] + steps: + - name: Git checkout + uses: actions/checkout@v5 + + - name: Download frontend digest artifacts + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: frontend-*-digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get image digest + id: digest + run: | + # Get the first digest from artifacts (we'll test with amd64) + DIGEST=$(ls /tmp/digests | head -1) + echo "digest=sha256:${DIGEST}" >> $GITHUB_OUTPUT + + - name: Test frontend image with ${{ matrix.environment }} environment + run: | + # Pull the image by digest + docker pull ${{ env.REGISTRY_FRONTEND_IMAGE }}@${{ steps.digest.outputs.digest }} + + # Tag it for easier reference + docker tag ${{ env.REGISTRY_FRONTEND_IMAGE }}@${{ steps.digest.outputs.digest }} test-frontend:${{ matrix.environment }} + + # Run the test script + ./.github/scripts/docker/test-frontend-docker.sh test-frontend:${{ matrix.environment }} ${{ matrix.environment }} + # Push frontend multi-arch manifest push-frontend-image: runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/release') needs: - - build-mina-frontend-image + - test-frontend-image steps: - name: Git checkout uses: actions/checkout@v5 diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index e9749ef16d..1dac8ec7fd 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -3,7 +3,6 @@ on: push: branches: [ main, develop ] pull_request: - paths: [ "frontend/**", ".github/frontend.yaml" ] workflow_dispatch: concurrency: diff --git a/.github/workflows/test-docs-scripts-frontend.yaml b/.github/workflows/test-docs-scripts-frontend.yaml index a49c0e56e5..d5b6b53ea0 100644 --- a/.github/workflows/test-docs-scripts-frontend.yaml +++ b/.github/workflows/test-docs-scripts-frontend.yaml @@ -67,40 +67,46 @@ jobs: fi bash website/docs/developers/scripts/frontend/install-dependencies.sh - - name: Test build development script + - name: Test build production script run: | # Source nvm if available (Linux) if [ -n "$NVM_DIR" ] && [ -s "$NVM_DIR/nvm.sh" ]; then \. "$NVM_DIR/nvm.sh" fi - bash website/docs/developers/scripts/frontend/build-development.sh + bash website/docs/developers/scripts/frontend/build-production.sh - - name: Test build production script + - name: Test format code script run: | # Source nvm if available (Linux) if [ -n "$NVM_DIR" ] && [ -s "$NVM_DIR/nvm.sh" ]; then \. "$NVM_DIR/nvm.sh" fi - bash website/docs/developers/scripts/frontend/build-production.sh + bash website/docs/developers/scripts/frontend/format-code.sh - - name: Test format code script + - name: Test build leaderboard script run: | # Source nvm if available (Linux) if [ -n "$NVM_DIR" ] && [ -s "$NVM_DIR/nvm.sh" ]; then \. "$NVM_DIR/nvm.sh" fi - bash website/docs/developers/scripts/frontend/format-code.sh + bash website/docs/developers/scripts/frontend/build-leaderboard.sh + + - name: Test build webnode script + run: | + # Source nvm if available (Linux) + if [ -n "$NVM_DIR" ] && [ -s "$NVM_DIR/nvm.sh" ]; then + \. "$NVM_DIR/nvm.sh" + fi + bash website/docs/developers/scripts/frontend/build-webnode.sh - name: Prepare for Cypress tests (Ubuntu 22.04) if: matrix.os == 'ubuntu-22.04' run: | - sudo apt-get update - sudo apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev \ - libnss3 libxss1 libasound2 libxtst6 xauth xvfb + bash website/docs/developers/scripts/frontend/install-cypress-deps-ubuntu-22-04.sh - name: Prepare for Cypress tests (Ubuntu 24.04) if: matrix.os == 'ubuntu-24.04' run: | - sudo apt-get update - sudo apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev \ - libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb + bash website/docs/developers/scripts/frontend/install-cypress-deps-ubuntu-24-04.sh + + # Note: Cypress works out of the box on macOS, no additional dependencies needed diff --git a/.gitignore b/.gitignore index 8156da434c..a6dfba52c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.env.docker /target /node/testing/res/ circuit-blobs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ea7f53435..278fbf6983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#1468](https://github.com/o1-labs/mina-rust/pull/1468)) - **Frontend**: define one Makefile target per build configuration ([#1469](https://github.com/o1-labs/mina-rust/pull/1469)) +- **Frontend**: revamping the Docker image, and provide a better support and + documentation for the different configuration. + ([#1473](https://github.com/o1-labs/mina-rust/pull/1473)) ### Changed diff --git a/Makefile b/Makefile index ee32daf1bf..3bf8711561 100644 --- a/Makefile +++ b/Makefile @@ -231,13 +231,13 @@ lint-bash: ## Check all shell scripts using shellcheck .PHONY: lint-dockerfiles lint-dockerfiles: ## Check all Dockerfiles using hadolint @if [ "$$GITHUB_ACTIONS" = "true" ]; then \ - OUTPUT=$$(find . -name "Dockerfile*" -type f -exec hadolint {} \;); \ + OUTPUT=$$(find . -name "Dockerfile*" -type f -not -path "*/node_modules/*" -exec hadolint {} \;); \ if [ -n "$$OUTPUT" ]; then \ echo "$$OUTPUT"; \ exit 1; \ fi; \ else \ - OUTPUT=$$(find . -name "Dockerfile*" -type f -exec sh -c 'docker run --rm -i hadolint/hadolint < "$$1"' _ {} \;); \ + OUTPUT=$$(find . -name "Dockerfile*" -type f -not -path "*/node_modules/*" -exec sh -c 'docker run --rm -i hadolint/hadolint < "$$1"' _ {} \;); \ if [ -n "$$OUTPUT" ]; then \ echo "$$OUTPUT"; \ exit 1; \ @@ -260,6 +260,8 @@ setup-wasm: ## Setup the WebAssembly toolchain, using nightly *) echo "Unsupported architecture: $$ARCH" && exit 1 ;; \ esac; \ TARGET="$$ARCH_PART-$$OS_PART"; \ + echo "Installing nightly toolchain: ${NIGHTLY_RUST_VERSION}-$$TARGET"; \ + rustup toolchain install ${NIGHTLY_RUST_VERSION}-$$TARGET; \ echo "Installing components for ${NIGHTLY_RUST_VERSION}-$$TARGET with wasm32 target"; \ rustup component add rust-src --toolchain ${NIGHTLY_RUST_VERSION}-$$TARGET; \ rustup component add rustfmt --toolchain ${NIGHTLY_RUST_VERSION}-$$TARGET; \ diff --git a/frontend/Dockerfile b/frontend/Dockerfile index dc37827175..dbd6a9c2f6 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,31 +1,140 @@ -FROM node:23 AS BUILD_IMAGE -# Doesn't matter what we put here - it get's overwritten by the docker \ -# build command -ARG BUILD_CONFIGURATION=production -WORKDIR /app -COPY . . -RUN npm install && \ - node_modules/.bin/ng build --configuration \ - ${BUILD_CONFIGURATION} && \ - npm prune --production && \ - echo "---------- USING APACHE ----------" +# ============================================================================= +# Mina Rust Frontend Docker Image +# ============================================================================= +# +# This Dockerfile creates a production-ready Apache HTTP server image that +# serves the Mina frontend application. +# +# IMAGE STAGES: +# 1. build_wasm - Builds WebAssembly files for web node functionality +# 2. wasm_files - Extracts only WASM artifacts (size optimization) +# 3. final - Apache HTTP server with source code and runtime build capability +# +# FEATURES: +# - Runtime frontend building based on environment variable +# - Pre-built WASM files for Mina web node +# - Environment-specific configuration support (local, webnode, prod, etc.) +# - Caching optimization for web assets +# - WebRTC signaling configuration +# +# BUILD ARGUMENTS: +# None - all configuration is done at runtime via environment variables +# +# REQUIRED FILES: +# .env.docker - Environment variables for VERGEN (git information) +# Generated by running: ./generate-docker-env.sh +# +# REQUIRED ENVIRONMENT VARIABLES: +# MINA_FRONTEND_ENVIRONMENT - Frontend configuration (local|webnode|production|development|producer|fuzzing) +# +# USAGE: +# # Generate .env.docker with current git information +# ./generate-docker-env.sh +# +# # Build frontend image +# docker build -t mina-frontend -f frontend/Dockerfile . +# +# # Run with specific environment (MINA_FRONTEND_ENVIRONMENT is REQUIRED) +# docker run -p 80:80 -e MINA_FRONTEND_ENVIRONMENT=webnode mina-frontend +# docker run -p 80:80 -e MINA_FRONTEND_ENVIRONMENT=local mina-frontend +# docker run -p 80:80 -e MINA_FRONTEND_ENVIRONMENT=production mina-frontend +# +# ============================================================================= + +# Build stage for WASM files +FROM rust:1.84.0-bullseye AS build_wasm + +# Install system dependencies required for building the wasm files +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config \ + libssl-dev \ + protobuf-compiler \ + make \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace +# Copy Makefile, environment file, and setup WASM toolchain +COPY Makefile ./ +COPY .env.docker ./ +RUN make setup-wasm -FROM httpd:2.4 +# Copy source code required for WASM build +COPY Cargo.toml Cargo.lock ./ +COPY cli ./cli +COPY core ./core +COPY fuzzer ./fuzzer +COPY genesis_ledgers ./genesis_ledgers +COPY ledger ./ledger +COPY macros ./macros +COPY mina-p2p-messages ./mina-p2p-messages +COPY node ./node +COPY p2p ./p2p +COPY poseidon ./poseidon +COPY producer-dashboard ./producer-dashboard +COPY snark ./snark +COPY tools ./tools +COPY vrf ./vrf +# Build WebAssembly using Makefile (source VERGEN variables from .env.docker) +RUN bash -c 'set -a && source .env.docker && make build-wasm' + +# WASM files extraction stage +FROM scratch AS wasm_files +WORKDIR /pkg +COPY --from=build_wasm /workspace/pkg ./ + +# Final stage - Apache with source code and build capability +FROM httpd:2.4 AS final + +# Install system dependencies required for Node.js and build tools # hadolint ignore=DL3008 RUN apt-get update && \ - apt-get install -y --no-install-recommends curl && \ - rm -rf /var/lib/apt/lists/* + apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + gnupg \ + make \ + && rm -rf /var/lib/apt/lists/* -COPY --from=BUILD_IMAGE /app/dist/frontend/browser \ - /usr/local/apache2/htdocs/ -COPY --from=BUILD_IMAGE /app/httpd.conf \ - /usr/local/apache2/conf/httpd.conf +WORKDIR /app -COPY docker/startup.sh /usr/local/bin/startup.sh -RUN chmod +x /usr/local/bin/startup.sh +# Copy documentation scripts for Node.js setup +COPY ./website/docs/developers/scripts/frontend/install-nodejs-linux.sh ./scripts/install-nodejs-linux.sh +COPY ./website/docs/developers/scripts/frontend/install-angular-cli.sh ./scripts/install-angular-cli.sh -ENTRYPOINT ["/usr/local/bin/startup.sh"] +# Install Node.js and Angular CLI using documentation scripts +RUN chmod +x ./scripts/*.sh && \ + bash ./scripts/install-nodejs-linux.sh && \ + bash -c 'source "$HOME/.nvm/nvm.sh" && bash ./scripts/install-angular-cli.sh' +# Copy entire frontend source code and Makefile +COPY frontend/ ./frontend/ +COPY Makefile ./ + +# Copy Apache configuration +COPY frontend/httpd.conf /usr/local/apache2/conf/httpd.conf + +# Copy WASM files from build stage +COPY --from=wasm_files . /usr/local/apache2/htdocs/assets/webnode/pkg/ + +# Copy circuit blobs for devnet and mainnet from local directory +COPY circuit-blobs/3.0.1devnet /usr/local/apache2/htdocs/assets/webnode/circuit-blobs/3.0.1devnet +COPY circuit-blobs/3.0.0mainnet /usr/local/apache2/htdocs/assets/webnode/circuit-blobs/3.0.0mainnet + +# Create startup script and setup directories +COPY ./frontend/docker/startup.sh /usr/local/bin/startup.sh +RUN chmod +x /usr/local/bin/startup.sh && \ + mkdir -p /usr/local/apache2/htdocs + +# Install frontend dependencies using Makefile +WORKDIR /app/frontend +RUN bash -c 'source "$HOME/.nvm/nvm.sh" && make install-deps' + +WORKDIR /app + +# MINA_FRONTEND_ENVIRONMENT is mandatory and must be set at runtime + +ENTRYPOINT ["/usr/local/bin/startup.sh"] CMD ["httpd-foreground"] diff --git a/frontend/Makefile b/frontend/Makefile index b79e66c1dd..79fe833b8e 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -11,38 +11,66 @@ help: ## Display this help message build: ## Build the frontend npx ng build -.PHONY: build-development -build-development: ## Build the frontend with development configuration - npx ng build --configuration development - .PHONY: build-fuzzing build-fuzzing: ## Build the frontend with fuzzing configuration npx ng build --configuration fuzzing + cp dist/frontend/browser/assets/environments/fuzzing.js \ + dist/frontend/browser/assets/environments/env.js .PHONY: build-local build-local: ## Build the frontend with local configuration npx ng build --configuration local + cp dist/frontend/browser/assets/environments/local.js \ + dist/frontend/browser/assets/environments/env.js -.PHONY: build-prod -build-prod: ## Build the frontend for production - npx ng build --configuration production - -.PHONY: build-prod-sentry -build-prod-sentry: ## Build the frontend for production with Sentry sourcemaps +.PHONY: build-production-sentry +build-production-sentry: ## Build the frontend for production with Sentry sourcemaps npx ng build --configuration production + cp dist/frontend/browser/assets/environments/production.js \ + dist/frontend/browser/assets/environments/env.js $(MAKE) sentry-sourcemaps .PHONY: build-producer build-producer: ## Build the frontend with producer configuration npx ng build --configuration producer + cp dist/frontend/browser/assets/environments/producer.js \ + dist/frontend/browser/assets/environments/env.js .PHONY: build-production build-production: ## Build the frontend with production configuration npx ng build --configuration production + cp dist/frontend/browser/assets/environments/production.js \ + dist/frontend/browser/assets/environments/env.js + +.PHONY: build-webnode +build-webnode: ## Build the frontend with webnode configuration + npx ng build --configuration webnode-local + cp dist/frontend/browser/assets/environments/webnode.js \ + dist/frontend/browser/assets/environments/env.js -.PHONY: build-webnodelocal -build-webnodelocal: ## Build the frontend with webnodelocal configuration - npx ng build --configuration webnodelocal +.PHONY: build-leaderboard +build-leaderboard: build-production ## Build the frontend with leaderboard configuration + cp dist/frontend/browser/assets/environments/leaderboard.js \ + dist/frontend/browser/assets/environments/env.js + +.PHONY: build-staging +build-staging: ## Build the frontend with staging configuration + npx ng build --configuration production + cp dist/frontend/browser/assets/environments/staging.js \ + dist/frontend/browser/assets/environments/env.js + +.PHONY: build-block-producers +build-block-producers: ## Build the frontend with block-producers configuration + npx ng build --configuration production + cp dist/frontend/browser/assets/environments/block-producers.js \ + dist/frontend/browser/assets/environments/env.js + +.PHONY: build-leaderboard-sentry +build-leaderboard-sentry: ## Build the frontend with leaderboard configuration and Sentry sourcemaps + npx ng build --configuration production + cp dist/frontend/browser/assets/environments/leaderboard.js \ + dist/frontend/browser/assets/environments/env.js + $(MAKE) sentry-sourcemaps .PHONY: check-prettify check-prettify: ## Check if files are formatted with Prettier @@ -58,38 +86,17 @@ copy-env: ## Copy webnode.js to env.js dist/frontend/browser/assets/environments/env.js .PHONY: deploy -deploy: ## Deploy the application - $(MAKE) prebuild - $(MAKE) build-prod-sentry - $(MAKE) copy-env +deploy: prebuild build-production-sentry copy-env ## Deploy the application firebase deploy .PHONY: deploy-leaderboard -deploy-leaderboard: ## Deploy the leaderboard application - $(MAKE) prebuild - $(MAKE) build-prod - cp dist/frontend/browser/assets/environments/leaderboard.js \ - dist/frontend/browser/assets/environments/env.js +deploy-leaderboard: prebuild build-leaderboard ## Deploy the leaderboard application firebase deploy .PHONY: deploy-leaderboard-sentry -deploy-leaderboard-sentry: ## Deploy the leaderboard application with Sentry sourcemaps - $(MAKE) prebuild - $(MAKE) build-prod-sentry - cp dist/frontend/browser/assets/environments/leaderboard.js \ - dist/frontend/browser/assets/environments/env.js +deploy-leaderboard-sentry: prebuild build-leaderboard-sentry ## Deploy the leaderboard application with Sentry sourcemaps firebase deploy -.PHONY: docker -docker: ## Build and push Docker image - $(MAKE) build-prod-sentry - docker buildx build --platform linux/amd64 -t openmina/frontend:latest . - docker push openmina/frontend:latest - -.PHONY: install -install: ## Install npm dependencies - npm install - .PHONY: install-deps install-deps: ## Install npm dependencies (alias) npm install @@ -103,7 +110,7 @@ prettify: ## Format HTML, SCSS, TypeScript, and JavaScript files with Prettier npx prettier --write 'src/**/*.{ts,js,html,scss,css,json}' .PHONY: rebuild -rebuild: clean install build ## Clean, reinstall dependencies, and rebuild +rebuild: clean install-deps build ## Clean, reinstall dependencies, and rebuild .PHONY: sentry-sourcemaps sentry-sourcemaps: ## Upload sourcemaps to Sentry @@ -113,8 +120,7 @@ sentry-sourcemaps: ## Upload sourcemaps to Sentry ./dist/frontend/browser .PHONY: start -start: ## Start the development server - npm install +start: install-deps ## Start the development server npx ng serve --configuration local --open .PHONY: start-bundle @@ -123,15 +129,10 @@ start-bundle: ## Serve the built bundle .PHONY: start-dev-mobile start-dev-mobile: ## Start the development server for mobile (accessible on network) - npx ng serve --configuration development --host 0.0.0.0 - -.PHONY: start-development -start-development: ## Start the development server with development configuration - npx ng serve --configuration development + npx ng serve --configuration local --host 0.0.0.0 .PHONY: start-fuzzing -start-fuzzing: ## Start the fuzzing build and serve - $(MAKE) install-deps +start-fuzzing: install-deps ## Start the fuzzing build and serve npx ng build --configuration fuzzing $(MAKE) start-bundle @@ -144,13 +145,8 @@ start-production: ## Start the development server with production configuration npx ng serve --configuration production .PHONY: start-webnode -start-webnode: ## Start the webnode development server - npm install - npx ng serve --configuration webnodelocal --open - -.PHONY: start-webnodelocal -start-webnodelocal: ## Start the development server with webnodelocal configuration - npx ng serve --configuration webnodelocal +start-webnode: install-deps ## Start the webnode development server + npx ng serve --configuration webnode-local --open .PHONY: test test: ## Run Cypress tests @@ -158,4 +154,4 @@ test: ## Run Cypress tests .PHONY: test-headless test-headless: ## Run Cypress tests in headless mode - npx cypress run --headless --config baseUrl=http://localhost:4200 \ No newline at end of file + npx cypress run --headless --config baseUrl=http://localhost:4200 diff --git a/frontend/angular.json b/frontend/angular.json index 712a6e2dec..19bd64548d 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -89,11 +89,11 @@ ], "outputHashing": "all" }, - "webnodelocal": { + "webnode-local": { "fileReplacements": [ { "replace": "src/environments/environment.ts", - "with": "src/environments/environment.webnodelocal.ts" + "with": "src/environments/environment.webnode-local.ts" } ], "outputHashing": "all" @@ -128,8 +128,8 @@ "development": { "buildTarget": "frontend:build:development" }, - "webnodelocal": { - "buildTarget": "frontend:build:webnodelocal" + "webnode-local": { + "buildTarget": "frontend:build:webnode-local" }, "local": { "buildTarget": "frontend:build:local" diff --git a/frontend/docker/generate-docker-env.sh b/frontend/docker/generate-docker-env.sh new file mode 100755 index 0000000000..2535e84fb8 --- /dev/null +++ b/frontend/docker/generate-docker-env.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Generate .env.docker file with git information for Docker builds +# This script must be run from the project root before building the Docker image + +set -e + +# Navigate to project root (two levels up from frontend/docker/) +cd "$(dirname "$0")/../.." + +# Extract git information +GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +GIT_COMMIT_AUTHOR_EMAIL=$(git log -1 --pretty=format:'%ae') +GIT_COMMIT_AUTHOR_NAME=$(git log -1 --pretty=format:'%an') +GIT_COMMIT_COUNT=$(git rev-list --count HEAD) +GIT_COMMIT_DATE=$(git log -1 --pretty=format:'%cd' --date=short) +GIT_COMMIT_MESSAGE=$(git log -1 --pretty=format:'%s') +GIT_COMMIT_TIMESTAMP=$(git log -1 --pretty=format:'%cI') +GIT_DESCRIBE=$(git describe --tags --always --dirty 2>/dev/null || echo "v0.0.0-$(git rev-parse --short HEAD)") +GIT_SHA=$(git rev-parse HEAD) + +# Extract build information +BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +RUSTC_VERSION=$(rustc --version 2>/dev/null || echo "unknown") +RUSTC_CHANNEL=$(echo "$RUSTC_VERSION" | grep -o '\(stable\|beta\|nightly\)' || echo "stable") +RUSTC_SEMVER=$(echo "$RUSTC_VERSION" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' || echo "0.0.0") +TARGET_TRIPLE=$(rustc --version --verbose 2>/dev/null | grep "host:" | cut -d' ' -f2 || echo "unknown") + +# Extract Cargo information from current build context +CARGO_FEATURES="${CARGO_FEATURES:-default}" +CARGO_OPT_LEVEL="${CARGO_OPT_LEVEL:-0}" +CARGO_TARGET_TRIPLE="${CARGO_BUILD_TARGET:-$TARGET_TRIPLE}" +CARGO_DEBUG="${CARGO_DEBUG:-true}" + +# Write to .env.docker using grouped commands +{ + echo "# VERGEN environment variables for Docker builds" + echo "# Generated automatically from git and build information" + echo "# Do not edit this file manually - run ./generate-docker-env.sh instead" + echo "" + echo "# Git information" + echo "VERGEN_GIT_BRANCH=$GIT_BRANCH" + echo "VERGEN_GIT_COMMIT_AUTHOR_EMAIL=$GIT_COMMIT_AUTHOR_EMAIL" + echo "VERGEN_GIT_COMMIT_AUTHOR_NAME=$GIT_COMMIT_AUTHOR_NAME" + echo "VERGEN_GIT_COMMIT_COUNT=$GIT_COMMIT_COUNT" + echo "VERGEN_GIT_COMMIT_DATE=$GIT_COMMIT_DATE" + echo "VERGEN_GIT_COMMIT_MESSAGE=$GIT_COMMIT_MESSAGE" + echo "VERGEN_GIT_COMMIT_TIMESTAMP=$GIT_COMMIT_TIMESTAMP" + echo "VERGEN_GIT_DESCRIBE=$GIT_DESCRIBE" + echo "VERGEN_GIT_SHA=$GIT_SHA" + echo "" + echo "# Build information" + echo "VERGEN_BUILD_TIMESTAMP=$BUILD_TIMESTAMP" + echo "" + echo "# Cargo information" + echo "VERGEN_CARGO_FEATURES=$CARGO_FEATURES" + echo "VERGEN_CARGO_OPT_LEVEL=$CARGO_OPT_LEVEL" + echo "VERGEN_CARGO_TARGET_TRIPLE=$CARGO_TARGET_TRIPLE" + echo "VERGEN_CARGO_DEBUG=$CARGO_DEBUG" + echo "" + echo "# Rustc information" + echo "VERGEN_RUSTC_CHANNEL=$RUSTC_CHANNEL" + echo "VERGEN_RUSTC_SEMVER=$RUSTC_SEMVER" + echo "VERGEN_RUSTC_HOST_TRIPLE=$TARGET_TRIPLE" + echo "VERGEN_RUSTC_COMMIT_DATE=unknown" + echo "VERGEN_RUSTC_COMMIT_HASH=unknown" + echo "VERGEN_RUSTC_LLVM_VERSION=unknown" +} > .env.docker + +echo "Generated .env.docker with build information:" +echo "- Branch: $GIT_BRANCH" +echo "- Commit: $GIT_SHA" +echo "- Author: $GIT_COMMIT_AUTHOR_NAME <$GIT_COMMIT_AUTHOR_EMAIL>" +echo "- Message: $GIT_COMMIT_MESSAGE" +echo "- Build Time: $BUILD_TIMESTAMP" +echo "- Rust Channel: $RUSTC_CHANNEL" +echo "- Rust Version: $RUSTC_SEMVER" +echo "- Target: $CARGO_TARGET_TRIPLE" \ No newline at end of file diff --git a/frontend/docker/startup.sh b/frontend/docker/startup.sh old mode 100644 new mode 100755 index 9d5d8e8b2e..13007e7ef6 --- a/frontend/docker/startup.sh +++ b/frontend/docker/startup.sh @@ -1,212 +1,83 @@ #!/bin/bash -MINA_BASE_URL="https://github.com/openmina" - -replace_signaling_url() { - if [ -n "$MINA_SIGNALING_URL" ]; then - HTTPD_CONF="/usr/local/apache2/conf/httpd.conf" - SIGNALING_URL="http://localhost:3000/mina/webrtc/signal" - - echo "Replacing signaling URL in $HTTPD_CONF..." - - sed -i "s|$SIGNALING_URL|$MINA_SIGNALING_URL|g" "$HTTPD_CONF" - sed_exit_code=$? - if [[ $sed_exit_code -ne 0 ]]; then - echo "Failed to replace the signaling URL, exiting." +set -e + +# Function to build frontend based on environment +build_frontend() { + local environment="$1" + echo "Building frontend for environment: $environment" + + cd /app/frontend + + # Source NVM to make Node.js and npm available + export NVM_DIR="$HOME/.nvm" + # shellcheck disable=SC1091 + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + + # Map environment to Makefile targets and Angular configurations + case "$environment" in + "local") + # Uses Angular local configuration and local.js runtime + make build-local + ;; + "fuzzing") + # Uses Angular fuzzing configuration and fuzzing.js runtime + make build-fuzzing + ;; + "production") + # Uses Angular production configuration and production.js runtime + make build-production + ;; + "producer") + # Uses Angular producer configuration and producer.js runtime + make build-producer + ;; + "webnode") + # Uses Angular webnode-local configuration and webnode.js runtime + make build-webnode + ;; + "leaderboard") + # Uses Angular production configuration and leaderboard.js runtime + make build-leaderboard + ;; + "staging") + # Uses Angular production configuration with staging.js runtime + make build-staging + ;; + "block-producers") + # Uses Angular production configuration with block-producers.js runtime + make build-block-producers + ;; + *) + echo "Error: Unknown environment '$environment'" + echo "Available environments: local, fuzzing, production, producer, webnode, leaderboard, staging, block-producers" exit 1 - else - echo "Successfully replaced signaling URL with" \ - "'$MINA_SIGNALING_URL' in $HTTPD_CONF" - fi - else - echo "MINA_SIGNALING_URL is not set. No replacement performed." - fi -} - -download_circuit_files() { - CIRCUITS_BASE_URL="$MINA_BASE_URL/circuit-blobs/releases/download" - CIRCUITS_VERSION="3.0.1devnet" - - DEVNET_CIRCUIT_FILES=( - "block_verifier_index.postcard" - "transaction_verifier_index.postcard" - "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_gates.json" - "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_internal_vars.bin" - "step-step-proving-key-blockchain-snark-step-0-55f640777b6486a6fd3fdbc3fcffcc60_rows_rev.bin" - "step-step-proving-key-transaction-snark-merge-1-ba1d52dfdc2dd4d2e61f6c66ff2a5b2f_gates.json" - "step-step-proving-key-transaction-snark-merge-1-ba1d52dfdc2dd4d2e61f6c66ff2a5b2f_internal_vars.bin" - "step-step-proving-key-transaction-snark-merge-1-ba1d52dfdc2dd4d2e61f6c66ff2a5b2f_rows_rev.bin" - "step-step-proving-key-transaction-snark-opt_signed-3-9eefed16953d2bfa78a257adece02d47_gates.json" - "step-step-proving-key-transaction-snark-opt_signed-3-9eefed16953d2bfa78a257adece02d47_internal_vars.bin" - "step-step-proving-key-transaction-snark-opt_signed-3-9eefed16953d2bfa78a257adece02d47_rows_rev.bin" - "step-step-proving-key-transaction-snark-opt_signed-opt_signed-2-48925e6a97197028e1a7c1ecec09021d_gates.json" - "step-step-proving-key-transaction-snark-opt_signed-opt_signed-2-48925e6a97197028e1a7c1ecec09021d_internal_vars.bin" - "step-step-proving-key-transaction-snark-opt_signed-opt_signed-2-48925e6a97197028e1a7c1ecec09021d_rows_rev.bin" - "step-step-proving-key-transaction-snark-proved-4-0cafcbc6dffccddbc82f8c2519c16341_gates.json" - "step-step-proving-key-transaction-snark-proved-4-0cafcbc6dffccddbc82f8c2519c16341_internal_vars.bin" - "step-step-proving-key-transaction-snark-proved-4-0cafcbc6dffccddbc82f8c2519c16341_rows_rev.bin" - "step-step-proving-key-transaction-snark-transaction-0-c33ec5211c07928c87e850a63c6a2079_gates.json" - "step-step-proving-key-transaction-snark-transaction-0-c33ec5211c07928c87e850a63c6a2079_internal_vars.bin" - "step-step-proving-key-transaction-snark-transaction-0-c33ec5211c07928c87e850a63c6a2079_rows_rev.bin" - "wrap-wrap-proving-key-blockchain-snark-bbecaf158ca543ec8ac9e7144400e669_gates.json" - "wrap-wrap-proving-key-blockchain-snark-bbecaf158ca543ec8ac9e7144400e669_internal_vars.bin" - "wrap-wrap-proving-key-blockchain-snark-bbecaf158ca543ec8ac9e7144400e669_rows_rev.bin" - "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_gates.json" - "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_internal_vars.bin" - "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_rows_rev.bin" - ) - DOWNLOAD_DIR="/usr/local/apache2/htdocs/assets/webnode/circuit-blobs/$CIRCUITS_VERSION" - - mkdir -p "$DOWNLOAD_DIR" - - for FILE in "${DEVNET_CIRCUIT_FILES[@]}"; do - if [[ -f "$DOWNLOAD_DIR/$FILE" ]]; then - echo "$FILE already exists in $DOWNLOAD_DIR, skipping download." - else - echo "Downloading $FILE to $DOWNLOAD_DIR..." - curl -s -L --retry 3 --retry-delay 5 \ - -o "$DOWNLOAD_DIR/$FILE" \ - "$CIRCUITS_BASE_URL/$CIRCUITS_VERSION/$FILE" - curl_exit_code=$? - if [[ $curl_exit_code -ne 0 ]]; then - echo "Failed to download $FILE after 3 attempts, exiting." - exit 1 - else - echo "$FILE downloaded successfully to $DOWNLOAD_DIR" - fi - fi - done -} - -download_wasm_files() { - if [ -z "$MINA_WASM_VERSION" ]; then - echo "Error: MINA_WASM_VERSION is not set. Exiting." - exit 1 - fi - - WASM_URL="$MINA_BASE_URL/openmina/releases/download/\ -$MINA_WASM_VERSION/openmina-$MINA_WASM_VERSION-webnode-wasm.tar.gz" - TARGET_DIR="/usr/local/apache2/htdocs/assets/webnode/pkg" - - mkdir -p "$TARGET_DIR" - - echo "Downloading WASM files from $WASM_URL..." - curl -s -L --retry 3 --retry-delay 5 \ - -o "/tmp/openmina-$MINA_WASM_VERSION-webnode-wasm.tar.gz" \ - "$WASM_URL" - curl_exit_code=$? - if [[ $curl_exit_code -ne 0 ]]; then - echo "Failed to download the WASM file after 3 attempts, exiting." - exit 1 - else - echo "WASM file downloaded successfully. Extracting to $TARGET_DIR..." - - # Check if the extraction was successful - tar -xzf "/tmp/openmina-$MINA_WASM_VERSION-webnode-wasm.tar.gz" \ - -C "$TARGET_DIR" - tar_exit_code=$? - if [[ $tar_exit_code -ne 0 ]]; then - echo "Failed to extract the WASM file, exiting." - exit 1 - else - echo "WASM files extracted successfully to $TARGET_DIR" - - # Inject caching logic into openmina_node_web.js - MINA_JS="$TARGET_DIR/openmina_node_web.js" - INDEX_HTML="/usr/local/apache2/htdocs/index.html" - inject_caching_logic "$MINA_JS" "$INDEX_HTML" - fi - fi - - rm "/tmp/openmina-$MINA_WASM_VERSION-webnode-wasm.tar.gz" -} + ;; + esac -get_short_sha1() { - local file="$1" - if [ -z "$file" ]; then - echo "Usage: get_short_sha1 filename" >&2 - return 1 - fi - if [ ! -f "$file" ]; then - echo "Error: File not found: $file" >&2 - return 1 - fi + # Copy built files to Apache document root + echo "Copying built files to Apache document root..." + cp -r dist/frontend/browser/* /usr/local/apache2/htdocs/ - if command -v sha1sum >/dev/null 2>&1; then - sha1sum "$file" | awk '{ print substr($1,1,8) }' - elif command -v openssl >/dev/null 2>&1; then - openssl sha1 "$file" | awk '{ print substr($2,1,8) }' - else - echo "Error: Neither sha1sum nor openssl is available for hashing." >&2 - return 1 - fi + echo "Frontend build complete for environment: $environment" + cd /app } -inject_caching_logic() { - local js_file="$1" - local index_html="$2" - - # Check if JavaScript file exists - if [ ! -f "$js_file" ]; then - echo "Warning: $js_file not found. Caching logic not injected." - return 0 - fi - - echo "Injecting caching logic into $js_file" - - # Define target files - local js_target_file="${TARGET_DIR}/openmina_node_web.js" - local wasm_target_file="${TARGET_DIR}/openmina_node_web_bg.wasm" - - # Generate checksum hashes - local js_file_hash - js_file_hash=$(get_short_sha1 "$js_target_file") || { echo "Failed to get hash for $js_target_file"; return 1; } - - local wasm_file_hash - wasm_file_hash=$(get_short_sha1 "$wasm_target_file") || { echo "Failed to get hash for $wasm_target_file"; return 1; } - - # Check if hashed files already exist to prevent multiple injections - local js_new_file="${TARGET_DIR}/openmina_node_web.${js_file_hash}.js" - local wasm_new_file="${TARGET_DIR}/openmina_node_web_bg.${wasm_file_hash}.wasm" - - if [[ -f "$js_new_file" ]] && [[ -f "$wasm_new_file" ]]; then - echo "Hashed files already exist. Skipping caching logic injection." - return 0 - fi - - # Replace openmina_node_web_bg.wasm with openmina_node_web_bg..wasm in JS file - sed -i "s/openmina_node_web_bg\.wasm/openmina_node_web_bg.${wasm_file_hash}.wasm/g" "$js_file" || { echo "Failed to update wasm filename in $js_file"; return 1; } - - # Add cache headers to fetch calls in JS file - sed -i 's/module_or_path = fetch(module_or_path);/module_or_path = fetch(module_or_path, { cache: "force-cache", headers: { "Cache-Control": "max-age=31536000, immutable" } });/' "$js_file" || { echo "Failed to inject cache headers into $js_file"; return 1; } - - # Rename wasm file with hash - mv "$wasm_target_file" "$wasm_new_file" || { echo "Failed to rename $wasm_target_file to $wasm_new_file"; return 1; } - - # Rename JS file with hash - mv "$js_target_file" "$js_new_file" || { echo "Failed to rename $js_target_file to $js_new_file"; return 1; } - - # Replace JS filename in index.html - sed -i "s/openmina_node_web\.js/openmina_node_web.${js_file_hash}.js/g" "$index_html" || { echo "Failed to update JS filename in $index_html"; return 1; } - - echo "Successfully injected caching logic into $js_file" -} +# Validate that MINA_FRONTEND_ENVIRONMENT is set +if [ -z "$MINA_FRONTEND_ENVIRONMENT" ]; then + echo "Error: MINA_FRONTEND_ENVIRONMENT environment variable is required." + echo "Available environments: local, fuzzing, production, producer, webnode, leaderboard, staging, block-producers" + echo "Example: docker run -e MINA_FRONTEND_ENVIRONMENT=webnode mina-frontend" + exit 1 +fi -if [ -n "$MINA_FRONTEND_ENVIRONMENT" ]; then - echo "Using environment: $MINA_FRONTEND_ENVIRONMENT" - cp -f /usr/local/apache2/htdocs/assets/environments/"$MINA_FRONTEND_ENVIRONMENT".js \ - /usr/local/apache2/htdocs/assets/environments/env.js +echo "Using environment: $MINA_FRONTEND_ENVIRONMENT" - if [ "$MINA_FRONTEND_ENVIRONMENT" = "webnode" ]; then - echo "Environment is 'webnode'. Downloading circuit and WASM files..." - download_wasm_files - download_circuit_files - fi -else - echo "No environment specified. Using default." -fi +# Build the frontend for the specified environment +build_frontend "$MINA_FRONTEND_ENVIRONMENT" -replace_signaling_url +# Environment file is now copied by Makefile build targets +echo "Environment configuration set during build process" echo "Starting Apache..." exec "$@" diff --git a/frontend/package.json b/frontend/package.json index 7b83f1cfc6..3ae7213010 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,34 +3,27 @@ "version": "1.0.184", "scripts": { "build": "make build", - "build:dev": "make build-development", - "build:development": "make build-development", "build:fuzzing": "make build-fuzzing", "build:local": "make build-local", - "build:prod": "make build-prod", - "build:prod:sentry": "make build-prod-sentry", + "build:production:sentry": "make build-production-sentry", "build:producer": "make build-producer", "build:production": "make build-production", - "build:webnodelocal": "make build-webnodelocal", + "build:webnode": "make build-webnode", "check-prettify": "make check-prettify", "copy-env": "make copy-env", "deploy": "make deploy", "deploy:leaderboard": "make deploy-leaderboard", - "docker": "make docker", "install:deps": "make install-deps", "prebuild": "make prebuild", "prettify": "make prettify", "sentry:sourcemaps": "make sentry-sourcemaps", "start": "make start", "start:bundle": "make start-bundle", - "start:dev": "make start-development", "start:dev:mobile": "make start-dev-mobile", - "start:development": "make start-development", "start:fuzzing": "make start-fuzzing", "start:local": "make start-local", "start:production": "make start-production", "start:webnode": "make start-webnode", - "start:webnodelocal": "make start-webnodelocal", "tests": "make test", "tests:headless": "make test-headless" }, diff --git a/frontend/src/app/shared/constants/config.ts b/frontend/src/app/shared/constants/config.ts index 56a6f46e8a..cd8df1ba43 100644 --- a/frontend/src/app/shared/constants/config.ts +++ b/frontend/src/app/shared/constants/config.ts @@ -12,6 +12,8 @@ import { safelyExecuteInBrowser, } from '@openmina/shared'; +// NOTE: When modifying CONFIG or related functions, update the documentation at: +// website/docs/developers/frontend/environment-configuration.mdx export const CONFIG: Readonly = { ...environment, globalConfig: { @@ -33,7 +35,15 @@ safelyExecuteInBrowser(() => { }); export function getAvailableFeatures(config: MinaNode): FeatureType[] { - return Object.keys(getFeaturesConfig(config)) as FeatureType[]; + const featuresConfig = getFeaturesConfig(config); + if (!featuresConfig) { + console.warn('getAvailableFeatures: No features configuration found', { + config, + CONFIG, + }); + return []; + } + return Object.keys(featuresConfig) as FeatureType[]; } export function getFirstFeature( @@ -42,25 +52,49 @@ export function getFirstFeature( if (!isBrowser()) { return '' as FeatureType; } + + console.log('getFirstFeature called with config:', { + config, + 'CONFIG.configs': CONFIG.configs, + }); + if (Array.isArray(config?.features)) { return config.features[0]; } - return Object.keys(getFeaturesConfig(config))[0] as FeatureType; + const featuresConfig = getFeaturesConfig(config); + if (!featuresConfig) { + console.warn('getFirstFeature: No features configuration found', { + config, + CONFIG, + }); + return '' as FeatureType; + } + return Object.keys(featuresConfig)[0] as FeatureType; } export function isFeatureEnabled( config: MinaNode, feature: FeatureType, ): boolean { - if (Array.isArray(config.features)) { + if (Array.isArray(config?.features)) { return hasValue(config.features[0]); } - return hasValue(getFeaturesConfig(config)[feature]); + const featuresConfig = getFeaturesConfig(config); + if (!featuresConfig) { + console.warn('isFeatureEnabled: No features configuration found', { + config, + feature, + }); + return false; + } + return hasValue(featuresConfig[feature]); } -export function getFeaturesConfig(config: MinaNode): FeaturesConfig { +export function getFeaturesConfig( + config: MinaNode, +): FeaturesConfig | undefined { if (CONFIG.configs.length === 0) { return CONFIG.globalConfig?.features; } @@ -73,6 +107,14 @@ export function isSubFeatureEnabled( subFeature: string, ): boolean { const features = getFeaturesConfig(config); + if (!features) { + console.warn('isSubFeatureEnabled: No features configuration found', { + config, + feature, + subFeature, + }); + return false; + } return hasValue(features[feature]) && features[feature].includes(subFeature); } diff --git a/frontend/src/app/shared/types/core/environment/mina-env.type.ts b/frontend/src/app/shared/types/core/environment/mina-env.type.ts index 20dba6a975..7473e81fd6 100644 --- a/frontend/src/app/shared/types/core/environment/mina-env.type.ts +++ b/frontend/src/app/shared/types/core/environment/mina-env.type.ts @@ -7,7 +7,7 @@ * - Development: src/environments/environment.ts * - Production: src/environments/environment.prod.ts * - Local: src/environments/environment.local.ts - * - WebNode: src/environments/environment.webnodelocal.ts + * - WebNode: src/environments/environment.webnode-local.ts * - Producer: src/environments/environment.producer.ts * - Fuzzing: src/environments/environment.fuzzing.ts * diff --git a/frontend/src/assets/environments/compose-producer.js b/frontend/src/assets/environments/compose-producer.js deleted file mode 100644 index 9d2985587a..0000000000 --- a/frontend/src/assets/environments/compose-producer.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This configuration is used for lunching devnet rust nodes and user's own node to produce block. All inside a docker container. - * Todo: github documentation link - */ - -export default { - production: true, - identifier: 'Running in Docker', - canAddNodes: true, - globalConfig: { - features: { - dashboard: [], - 'block-production': ['won-slots'], - nodes: ['overview', 'live', 'bootstrap'], - state: ['actions'], - snarks: ['scan-state'], - }, - graphQL: '/openmina-node/graphql', - }, - configs: [ - { - name: 'Compose rust node', - url: '/openmina-node', - }, - ], -}; diff --git a/frontend/src/assets/environments/compose.js b/frontend/src/assets/environments/compose.js deleted file mode 100644 index 471511fc54..0000000000 --- a/frontend/src/assets/environments/compose.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * This configuration is used for lunching devnet rust nodes inside a docker container. - * https://github.com/openmina/openmina?tab=readme-ov-file#how-to-launch-the-node-with-docker-compose - */ - -export default { - production: true, - identifier: 'Running in Docker', - canAddNodes: true, - globalConfig: { - features: { - dashboard: [], - nodes: ['overview', 'live', 'bootstrap'], - state: ['actions'], - snarks: ['scan-state'], - }, - graphQL: '/openmina-node/graphql', - }, - configs: [ - { - name: 'Compose rust node', - url: '/openmina-node', - }, - ], -}; diff --git a/frontend/src/assets/environments/fuzzing.js b/frontend/src/assets/environments/fuzzing.js new file mode 100644 index 0000000000..4cf1429687 --- /dev/null +++ b/frontend/src/assets/environments/fuzzing.js @@ -0,0 +1,20 @@ +/** + * This configuration is used for fuzzing test environment. + * Connects to fuzzing test nodes for stress testing. + */ + +export default { + production: false, + globalConfig: { + features: { + dashboard: [], + 'block-production': ['overview', 'won-slots'], + }, + }, + configs: [ + { + name: 'Fuzzing Node', + url: 'http://localhost:3085', + }, + ], +}; diff --git a/frontend/src/assets/environments/local.js b/frontend/src/assets/environments/local.js new file mode 100644 index 0000000000..09ed86c2e7 --- /dev/null +++ b/frontend/src/assets/environments/local.js @@ -0,0 +1,20 @@ +/** + * This configuration is used for local development environment. + * Connects to a local Mina node running on localhost. + */ + +export default { + production: false, + globalConfig: { + features: { + dashboard: [], + 'block-production': ['overview', 'won-slots'], + }, + }, + configs: [ + { + name: 'Local Node', + url: 'http://localhost:3085', + }, + ], +}; diff --git a/frontend/src/assets/environments/production.js b/frontend/src/assets/environments/production.js new file mode 100644 index 0000000000..0f0a50254b --- /dev/null +++ b/frontend/src/assets/environments/production.js @@ -0,0 +1,32 @@ +/** + * This configuration is used for production environment. + * Connects to production network endpoints. + */ + +export default { + production: true, + globalConfig: { + features: { + dashboard: [], + nodes: ['overview', 'live', 'bootstrap'], + state: ['actions'], + snarks: ['scan-state', 'work-pool'], + mempool: [], + 'block-production': ['won-slots'], + }, + }, + configs: [ + { + name: 'o1Labs Plain Node 1', + url: 'http://mina-rust-plain-1.gcp.o1test.net/graphql', + }, + { + name: 'o1Labs Plain Node 2', + url: 'http://mina-rust-plain-2.gcp.o1test.net/graphql', + }, + { + name: 'o1Labs Plain Node 3', + url: 'http://mina-rust-plain-3.gcp.o1test.net/graphql', + }, + ], +}; diff --git a/frontend/src/assets/environments/webnode.js b/frontend/src/assets/environments/webnode.js index 6f8704bf8b..dec358aabc 100644 --- a/frontend/src/assets/environments/webnode.js +++ b/frontend/src/assets/environments/webnode.js @@ -1,5 +1,8 @@ /** * This configuration is used for the staging-webnode environment. + * + * NOTE: When modifying environment configuration files, update the documentation at: + * website/docs/developers/frontend/environment-configuration.mdx */ export default { diff --git a/frontend/src/environments/environment.prod.ts b/frontend/src/environments/environment.prod.ts index f1f0b8ef10..39e949a2ad 100644 --- a/frontend/src/environments/environment.prod.ts +++ b/frontend/src/environments/environment.prod.ts @@ -1,17 +1,40 @@ import { MinaEnv } from '@shared/types/core/environment/mina-env.type'; -const env = typeof window !== 'undefined' ? (window as any).env : {}; +// This environment provides build-time configuration for production +// The actual runtime configuration gets loaded from /environments/env.js export const environment: Readonly = { production: true, - configs: env.configs, - globalConfig: env.globalConfig, - hideNodeStats: env.hideNodeStats, - identifier: env.identifier, - hideToolbar: env.hideToolbar, - canAddNodes: env.canAddNodes, - showWebNodeLandingPage: env.showWebNodeLandingPage, - showLeaderboard: env.showLeaderboard, - hidePeersPill: env.hidePeersPill, - hideTxPill: env.hideTxPill, - sentry: env.sentry, + identifier: 'Production', + canAddNodes: false, + hideNodeStats: false, + hideToolbar: false, + showWebNodeLandingPage: false, + showLeaderboard: false, + hidePeersPill: false, + hideTxPill: false, + globalConfig: { + features: { + dashboard: [], + nodes: ['overview', 'live', 'bootstrap'], + state: ['actions'], + snarks: ['scan-state', 'work-pool'], + mempool: [], + 'block-production': ['won-slots'], + }, + }, + configs: [ + { + name: 'o1Labs Plain Node 1', + url: 'http://mina-rust-plain-1.gcp.o1test.net/', + }, + { + name: 'o1Labs Plain Node 2', + url: 'http://mina-rust-plain-2.gcp.o1test.net/', + }, + { + name: 'o1Labs Plain Node 3', + url: 'http://mina-rust-plain-3.gcp.o1test.net/', + }, + ], + sentry: undefined, }; diff --git a/frontend/src/environments/environment.webnodelocal.ts b/frontend/src/environments/environment.webnode-local.ts similarity index 100% rename from frontend/src/environments/environment.webnodelocal.ts rename to frontend/src/environments/environment.webnode-local.ts diff --git a/frontend/src/index.html b/frontend/src/index.html index 5ada27e1c4..415960fdc2 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -2,6 +2,8 @@