update flake.nix #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Push Docker Images with Nix Flakes | |
| on: | |
| push: | |
| branches: [ main ] | |
| pull_request: | |
| branches: [ main ] | |
| schedule: | |
| - cron: '0 2 * * 1' # Weekly on Monday at 2 AM | |
| workflow_dispatch: | |
| inputs: | |
| push_images: | |
| description: 'Push images to registry' | |
| type: boolean | |
| default: true | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| security-events: write | |
| actions: read | |
| checks: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Nix 2.31.1 | |
| run: | | |
| # 安装最新稳定版 Nix 2.31.1 (单用户模式) | |
| sh <(curl -L https://nixos.org/nix/install) --no-daemon --yes | |
| # 重新加载环境 | |
| . /home/runner/.nix-profile/etc/profile.d/nix.sh | |
| # 验证安装 | |
| nix --version | |
| - name: Configure Nix | |
| run: | | |
| # 加载 Nix 环境 | |
| . /home/runner/.nix-profile/etc/profile.d/nix.sh | |
| # 配置 Nix 以支持 Flakes | |
| mkdir -p ~/.config/nix | |
| cat > ~/.config/nix/nix.conf << EOF | |
| experimental-features = nix-command flakes | |
| allow-import-from-derivation = true | |
| EOF | |
| # 验证配置 | |
| nix --version | |
| - name: Clean build environment | |
| run: | | |
| # 加载 Nix 环境 | |
| . /home/runner/.nix-profile/etc/profile.d/nix.sh | |
| # 清理 Nix 缓存 | |
| echo "🧹 Cleaning Nix cache..." | |
| nix-collect-garbage | |
| nix store gc | |
| # 清理 Docker 环境 | |
| echo "🧹 Cleaning Docker environment..." | |
| docker image prune -f | |
| docker container prune -f | |
| docker builder prune -f | |
| # 清理可能冲突的镜像 | |
| docker rmi ghcr.io/reaslab/docker-python-runner:secure-latest 2>/dev/null || echo " No existing image to remove" | |
| - name: Setup Nix Flake environment | |
| run: | | |
| # 加载 Nix 环境 | |
| . /home/runner/.nix-profile/etc/profile.d/nix.sh | |
| # 设置环境变量以允许非自由包(Gurobi) | |
| export NIXPKGS_ALLOW_UNFREE=1 | |
| # 进入 Nix 开发环境并构建 | |
| echo "🔧 Setting up Nix Flake environment..." | |
| nix develop --command bash -c " | |
| echo '🐍 Building Python Docker image with Nix Flakes...' | |
| # 使用与本地构建相同的方式 | |
| nix build .#docker-image --option sandbox false --impure --rebuild | |
| echo '✅ Build completed successfully' | |
| " | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to Container Registry | |
| if: github.event.inputs.push_images != 'false' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Load Docker image | |
| run: | | |
| # 使用 nix build 的结果加载 Docker 镜像 | |
| docker load < result | |
| - name: Tag Docker image | |
| if: github.event.inputs.push_images != 'false' | |
| run: | | |
| # Get the image ID from the loaded image | |
| IMAGE_ID=$(docker images --format "{{.ID}}" | head -1) | |
| echo "Image ID: $IMAGE_ID" | |
| # Create tags based on trigger type | |
| TAGS=() | |
| # Always create latest tag | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest") | |
| # Create version-specific tags based on trigger | |
| if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| # Main branch push: create timestamp and SHA tags | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-$(date +%Y%m%d-%H%M%S)") | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-${{ github.sha }}") | |
| elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| # Manual trigger: create timestamp tag only | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-$(date +%Y%m%d-%H%M%S)") | |
| elif [ "${{ github.event_name }}" = "schedule" ]; then | |
| # Scheduled: create timestamp tag only | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-$(date +%Y%m%d-%H%M%S)") | |
| fi | |
| # Tag with all tags | |
| for tag in "${TAGS[@]}"; do | |
| echo "Tagging with: $tag" | |
| docker tag "$IMAGE_ID" "$tag" | |
| done | |
| echo "All tags created successfully" | |
| - name: Push Docker image | |
| if: github.event.inputs.push_images != 'false' | |
| run: | | |
| # Create tags based on trigger type (same as tagging step) | |
| TAGS=() | |
| # Always create latest tag | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest") | |
| # Create version-specific tags based on trigger | |
| if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| # Main branch push: create timestamp and SHA tags | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-$(date +%Y%m%d-%H%M%S)") | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-${{ github.sha }}") | |
| elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| # Manual trigger: create timestamp tag only | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-$(date +%Y%m%d-%H%M%S)") | |
| elif [ "${{ github.event_name }}" = "schedule" ]; then | |
| # Scheduled: create timestamp tag only | |
| TAGS+=("${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-$(date +%Y%m%d-%H%M%S)") | |
| fi | |
| # Push all tags | |
| for tag in "${TAGS[@]}"; do | |
| echo "Pushing: $tag" | |
| docker push "$tag" | |
| done | |
| echo "All tags pushed successfully" | |
| - name: Run security scan | |
| if: github.event.inputs.push_images != 'false' | |
| uses: aquasecurity/trivy-action@master | |
| with: | |
| image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest | |
| format: 'sarif' | |
| output: 'trivy-results.sarif' | |
| continue-on-error: true | |
| - name: Install jq for SARIF parsing | |
| if: github.event.inputs.push_images != 'false' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq | |
| - name: Display local security scan results | |
| if: github.event.inputs.push_images != 'false' && always() | |
| run: | | |
| echo "## 🔍 Local Security Scan Results" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "trivy-results.sarif" ]; then | |
| echo "✅ Trivy security scan completed successfully" >> $GITHUB_STEP_SUMMARY | |
| # Extract vulnerability count | |
| VULNERABILITIES=$(jq '.runs[0].results | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| echo "- **Vulnerabilities found:** $VULNERABILITIES" >> $GITHUB_STEP_SUMMARY | |
| # Show high/critical vulnerabilities | |
| HIGH_CRITICAL=$(jq '.runs[0].results[] | select(.level == "error" or .level == "warning") | .level' trivy-results.sarif 2>/dev/null | wc -l || echo "0") | |
| echo "- **High/Critical issues:** $HIGH_CRITICAL" >> $GITHUB_STEP_SUMMARY | |
| # Show scan summary | |
| echo "### 📊 Scan Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Image scanned:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Scan format:** SARIF" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Results file:** \`trivy-results.sarif\`" >> $GITHUB_STEP_SUMMARY | |
| # Show some sample vulnerabilities if any | |
| if [ "$VULNERABILITIES" -gt 0 ]; then | |
| echo "### 🚨 Sample Vulnerabilities" >> $GITHUB_STEP_SUMMARY | |
| jq -r '.runs[0].results[0:3][] | "- **\(.level)**: \(.message.text)"' trivy-results.sarif 2>/dev/null >> $GITHUB_STEP_SUMMARY || echo "Unable to parse vulnerability details" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "### ✅ No vulnerabilities found" >> $GITHUB_STEP_SUMMARY | |
| echo "The Docker image appears to be secure!" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| else | |
| echo "⚠️ Security scan results not available" >> $GITHUB_STEP_SUMMARY | |
| echo "The Trivy scan may have failed or the results file was not generated." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Upload Trivy scan results | |
| if: github.event.inputs.push_images != 'false' && github.ref == 'refs/heads/main' | |
| uses: github/codeql-action/upload-sarif@v3 | |
| with: | |
| sarif_file: 'trivy-results.sarif' | |
| continue-on-error: true | |
| - name: Display security scan results | |
| if: github.event.inputs.push_images != 'false' && always() | |
| run: | | |
| echo "## 🔒 Security Scan Results" >> $GITHUB_STEP_SUMMARY | |
| # Check if Advanced Security is available | |
| if [ -f "trivy-results.sarif" ]; then | |
| echo "✅ Security scan completed successfully" >> $GITHUB_STEP_SUMMARY | |
| echo "📊 Scan results saved to trivy-results.sarif" >> $GITHUB_STEP_SUMMARY | |
| # Display scan summary | |
| echo "### 📋 Scan Summary" >> $GITHUB_STEP_SUMMARY | |
| if command -v jq >/dev/null 2>&1; then | |
| VULNERABILITIES=$(jq '.runs[0].results | length' trivy-results.sarif 2>/dev/null || echo "0") | |
| echo "- **Vulnerabilities found:** $VULNERABILITIES" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "### 📄 Full Report" >> $GITHUB_STEP_SUMMARY | |
| echo "Detailed scan results are available in the SARIF file." >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "⚠️ Security scan results not available" >> $GITHUB_STEP_SUMMARY | |
| echo "This may be due to:" >> $GITHUB_STEP_SUMMARY | |
| echo "- Advanced Security not enabled for this repository" >> $GITHUB_STEP_SUMMARY | |
| echo "- Scan failed to complete" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Note:** Code scanning requires GitHub Advanced Security (paid feature)." >> $GITHUB_STEP_SUMMARY | |
| echo "The Trivy scan still runs locally and results are available in the workflow logs." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| test: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: github.event.inputs.push_images != 'false' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Test Docker image functionality | |
| run: | | |
| echo "Testing Docker image functionality..." | |
| # 拉取镜像 | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest | |
| # 基本验证 | |
| echo "Image size: $(docker images --format '{{.Size}}' ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest)" | |
| echo "Image user: $(docker inspect --format='{{.Config.User}}' ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest)" | |
| # 测试容器创建 | |
| CONTAINER_ID=$(docker create ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest echo "test") | |
| if [ $? -eq 0 ]; then | |
| echo "✅ Container creation successful" | |
| docker rm $CONTAINER_ID | |
| else | |
| echo "❌ Container creation failed" | |
| exit 1 | |
| fi | |
| # 测试Python和UV(使用临时文件系统挂载) | |
| echo "Testing Python:" | |
| docker run --rm --tmpfs /tmp:noexec,nosuid,size=100m --user root ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest /bin/bash -c "export PATH=\${PATH} && python --version" || echo "Python not found" | |
| echo "Testing UV:" | |
| docker run --rm --tmpfs /tmp:noexec,nosuid,size=100m --user root ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:secure-latest /bin/bash -c "export PATH=\${PATH} && uv --version" || echo "UV not found" | |
| echo "✅ All tests completed successfully" | |
| generate-summary: | |
| runs-on: ubuntu-latest | |
| needs: [build, test] | |
| if: always() | |
| steps: | |
| - name: Generate summary | |
| run: | | |
| echo "## 🐳 Docker Image Build Summary (Nix Flakes)" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Image:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tags:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`secure-latest\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Build System:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Nix Flakes for reproducible builds" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Declarative environment management" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Features:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Secure Python 3.12 environment" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ UV package manager" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Gurobi optimization solver" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Scientific computing packages" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Non-root user execution" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Resource limits and security restrictions" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Security:**" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Trivy vulnerability scanning" >> $GITHUB_STEP_SUMMARY | |
| echo "- ⚠️ Code scanning requires GitHub Advanced Security (paid feature)" >> $GITHUB_STEP_SUMMARY | |
| echo "- ✅ Local security scan results available in workflow logs" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Registry:** [GitHub Container Registry](https://github.com/orgs/reaslab/packages)" >> $GITHUB_STEP_SUMMARY |