ci: Various Linux container and PyPi release improvements (#16)

* ci: release workflow fixes

* docs: Fix env var names

* docs: Improve Docker network configuration

* ci: Improve error checking in Docker release workflow

* ci: improve release workflow and container security

* ci: badges and better container scanning

* ci: Addition fixes

* ci: fix pypi release logic
This commit is contained in:
Sean P. Kane
2026-01-05 15:44:36 -08:00
committed by GitHub
parent c271630de0
commit f3c92ec413
8 changed files with 171 additions and 32 deletions
+33 -4
View File
@@ -113,13 +113,42 @@ jobs:
${{ env.IMAGE_NAME }}:test 2>&1 | \ ${{ env.IMAGE_NAME }}:test 2>&1 | \
grep -q '"result"' && echo "Container test passed" || echo "Container test completed" grep -q '"result"' && echo "Container test passed" || echo "Container test completed"
- name: Scan for vulnerabilities - name: Cache Trivy vulnerability database
if: github.event_name != 'pull_request' uses: actions/cache@v4
uses: aquasecurity/trivy-action@master
with: with:
image-ref: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} path: .trivy-cache
# Cache key based on OS and workflow file hash; refreshes when workflow changes
key: trivy-db-${{ runner.os }}-${{ hashFiles('.github/workflows/docker.yaml') }}
restore-keys: |
trivy-db-${{ runner.os }}-
- name: Scan for HIGH/CRITICAL vulnerabilities (fail build)
uses: aquasecurity/trivy-action@0.33.1
with:
image-ref: ${{ env.IMAGE_NAME }}:test
severity: "HIGH,CRITICAL"
exit-code: "1"
format: "table"
cache-dir: .trivy-cache
- name: Scan for MEDIUM/LOW vulnerabilities (warning only)
uses: aquasecurity/trivy-action@0.33.1
with:
image-ref: ${{ env.IMAGE_NAME }}:test
severity: "MEDIUM,LOW"
exit-code: "0"
format: "table"
cache-dir: .trivy-cache
continue-on-error: true
- name: Generate SARIF report for GitHub Security
if: github.event_name != 'pull_request'
uses: aquasecurity/trivy-action@0.33.1
with:
image-ref: ${{ env.IMAGE_NAME }}:test
format: "sarif" format: "sarif"
output: "trivy-results.sarif" output: "trivy-results.sarif"
cache-dir: .trivy-cache
continue-on-error: true continue-on-error: true
- name: Upload Trivy scan results - name: Upload Trivy scan results
+43 -6
View File
@@ -164,8 +164,10 @@ jobs:
name: Publish to TestPyPI name: Publish to TestPyPI
needs: [build, test-install] needs: [build, test-install]
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Only publish prereleases to TestPyPI # Only publish alpha releases to TestPyPI for early testing.
if: contains(github.ref, '-') # Beta and RC releases go to PyPI since they are closer to stable.
# This is intentional - do not change to contains(github.ref, '-').
if: contains(github.ref, '-alpha')
environment: environment:
name: testpypi name: testpypi
url: https://test.pypi.org/p/freecad-robust-mcp url: https://test.pypi.org/p/freecad-robust-mcp
@@ -188,8 +190,10 @@ jobs:
name: Publish to PyPI name: Publish to PyPI
needs: [build, test-install] needs: [build, test-install]
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Only publish stable releases to PyPI # Publish beta, rc, and stable releases to PyPI (not alpha).
if: ${{ !contains(github.ref, '-') }} # Alpha releases are too experimental for PyPI - they go to TestPyPI only.
# This is intentional - do not change to !contains(github.ref, '-').
if: ${{ !contains(github.ref, '-alpha') }}
environment: environment:
name: pypi name: pypi
url: https://pypi.org/p/freecad-robust-mcp url: https://pypi.org/p/freecad-robust-mcp
@@ -245,9 +249,10 @@ jobs:
echo "" echo ""
} >> "$GITHUB_STEP_SUMMARY" } >> "$GITHUB_STEP_SUMMARY"
if [[ "$REF_NAME" == *"-"* ]]; then # Alpha releases go to TestPyPI only; beta, rc, and stable go to PyPI
if [[ "$REF_NAME" == *"-alpha"* ]]; then
{ {
echo "**Type:** Prerelease (published to TestPyPI)" echo "**Type:** Alpha prerelease (published to TestPyPI only)"
echo "" echo ""
echo "### Install from TestPyPI" echo "### Install from TestPyPI"
echo "" echo ""
@@ -255,6 +260,38 @@ jobs:
echo "pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ freecad-robust-mcp" echo "pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ freecad-robust-mcp"
echo "\`\`\`" echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY" } >> "$GITHUB_STEP_SUMMARY"
elif [[ "$REF_NAME" == *"-beta"* ]]; then
{
echo "**Type:** Beta prerelease (published to PyPI)"
echo ""
echo "### Install from PyPI"
echo ""
echo "\`\`\`bash"
echo "pip install freecad-robust-mcp"
echo "\`\`\`"
echo ""
echo "Or with uv:"
echo ""
echo "\`\`\`bash"
echo "uv pip install freecad-robust-mcp"
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY"
elif [[ "$REF_NAME" == *"-rc"* ]]; then
{
echo "**Type:** Release candidate (published to PyPI)"
echo ""
echo "### Install from PyPI"
echo ""
echo "\`\`\`bash"
echo "pip install freecad-robust-mcp"
echo "\`\`\`"
echo ""
echo "Or with uv:"
echo ""
echo "\`\`\`bash"
echo "uv pip install freecad-robust-mcp"
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY"
else else
{ {
echo "**Type:** Stable release (published to PyPI)" echo "**Type:** Stable release (published to PyPI)"
+4
View File
@@ -97,6 +97,10 @@ cython_debug/
# Ruff # Ruff
.ruff_cache/ .ruff_cache/
# Trivy (pre-commit and CI cache)
.pre-commit-trivy-cache/
.trivy-cache/
# UV # UV
# Note: uv.lock is committed for reproducible CI builds # Note: uv.lock is committed for reproducible CI builds
+1
View File
@@ -11,6 +11,7 @@ uv = "0.9" # uv package manager
just = "1.43" # task runner just = "1.43" # task runner
pre-commit = "4.5" # pre-commit hooks pre-commit = "4.5" # pre-commit hooks
github-cli = "2.74" # GitHub CLI for PR/issue management github-cli = "2.74" # GitHub CLI for PR/issue management
trivy = "0.62" # container vulnerability scanner
[env] [env]
# FreeCAD connection mode: # FreeCAD connection mode:
+19 -3
View File
@@ -221,6 +221,21 @@ repos:
- id: hadolint-docker - id: hadolint-docker
name: hadolint (Dockerfile linter) name: hadolint (Dockerfile linter)
# ==========================================================================
# Dockerfile Security Scanning (Misconfigurations)
# ==========================================================================
- repo: https://github.com/mxab/pre-commit-trivy.git
rev: v0.16.0
hooks:
- id: trivyconfig-docker
name: trivy (Dockerfile misconfig)
args:
- --severity
- HIGH,CRITICAL
- --exit-code
- "1"
- .
# ========================================================================== # ==========================================================================
# Commit Message Linting # Commit Message Linting
# ========================================================================== # ==========================================================================
@@ -238,6 +253,7 @@ ci:
autoupdate_schedule: monthly autoupdate_schedule: monthly
autoupdate_commit_msg: "chore(deps): update pre-commit hooks" autoupdate_commit_msg: "chore(deps): update pre-commit hooks"
skip: skip:
- mypy # Needs dependencies installed - mypy # Needs dependencies installed
- hadolint-docker # Needs Docker - hadolint-docker # Needs Docker
- trufflehog # Can be slow in CI - trivyconfig-docker # Needs Docker
- trufflehog # Can be slow in CI
+24 -16
View File
@@ -3,6 +3,9 @@
# FreeCAD MCP Server Dockerfile # FreeCAD MCP Server Dockerfile
# Multi-stage build with BuildKit optimizations for multi-arch support # Multi-stage build with BuildKit optimizations for multi-arch support
# #
# Uses Alpine Linux for minimal image size and reduced CVE surface.
# Alpine has significantly fewer vulnerabilities than Debian-based images.
#
# Build: # Build:
# docker build -t freecad-mcp . # docker build -t freecad-mcp .
# #
@@ -15,24 +18,22 @@
# ============================================================================= # =============================================================================
# Stage 1: Builder - Install dependencies and build the package # Stage 1: Builder - Install dependencies and build the package
# ============================================================================= # =============================================================================
FROM python:3.11-slim AS builder FROM python:3.11-alpine AS builder
# Install build dependencies # Install build dependencies for compiling Python packages with native extensions
# hadolint ignore=DL3008 # hadolint ignore=DL3018
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN apk add --no-cache \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ build-base \
apt-get update && apt-get install -y --no-install-recommends \ libffi-dev
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Set up working directory # Set up working directory
WORKDIR /app WORKDIR /app
# Install uv for fast dependency management # Upgrade pip to fix CVE-2025-8869, then install uv for fast dependency management
# Using pip cache mount for faster rebuilds
# hadolint ignore=DL3013 # hadolint ignore=DL3013
RUN --mount=type=cache,target=/root/.cache/pip \ RUN --mount=type=cache,target=/root/.cache/pip \
pip install --no-compile uv pip install --no-cache-dir --upgrade "pip>=25.3" && \
pip install --no-cache-dir --no-compile uv
# Copy only dependency files first for better layer caching # Copy only dependency files first for better layer caching
COPY pyproject.toml README.md ./ COPY pyproject.toml README.md ./
@@ -53,7 +54,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
# ============================================================================= # =============================================================================
# Stage 2: Runtime - Minimal image for running the server # Stage 2: Runtime - Minimal image for running the server
# ============================================================================= # =============================================================================
FROM python:3.11-slim AS runtime FROM python:3.11-alpine AS runtime
# Labels for container metadata (OCI Image Spec) # Labels for container metadata (OCI Image Spec)
# Note: version, revision, and created are set dynamically in CI/CD workflows # Note: version, revision, and created are set dynamically in CI/CD workflows
@@ -65,11 +66,18 @@ LABEL org.opencontainers.image.title="FreeCAD MCP Server" \
org.opencontainers.image.licenses="MIT" \ org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="Sean P. Kane" \ org.opencontainers.image.vendor="Sean P. Kane" \
org.opencontainers.image.authors="Sean P. Kane <spkane@gmail.com>" \ org.opencontainers.image.authors="Sean P. Kane <spkane@gmail.com>" \
org.opencontainers.image.base.name="python:3.11-slim" org.opencontainers.image.base.name="python:3.11-alpine"
# Create non-root user for security # Create non-root user for security (Alpine uses addgroup/adduser)
RUN groupadd --gid 1000 mcpuser && \ RUN addgroup -g 1000 mcpuser && \
useradd --uid 1000 --gid 1000 --shell /bin/bash --create-home mcpuser adduser -u 1000 -G mcpuser -s /bin/sh -D mcpuser
# Upgrade system pip to fix CVE-2025-8869 (defense-in-depth)
# Note: Although PATH prefers /opt/venv/bin, we upgrade the system pip at
# /usr/local/bin/pip intentionally. This ensures no vulnerable pip exists
# in the image, even if the venv is bypassed or pip is invoked directly.
# hadolint ignore=DL3013
RUN pip install --no-cache-dir --upgrade "pip>=25.3"
# Copy virtual environment from builder # Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv COPY --from=builder /opt/venv /opt/venv
+14
View File
@@ -1,5 +1,10 @@
# FreeCAD Tools and MCP Server # FreeCAD Tools and MCP Server
[![CI Tests](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/test.yaml/badge.svg)](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/test.yaml)
[![Docker Build](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/docker.yaml/badge.svg)](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/docker.yaml)
[![Pre-commit](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/pre-commit.yaml/badge.svg)](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/pre-commit.yaml)
[![CodeQL](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/codeql.yaml/badge.svg)](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/codeql.yaml)
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that enables integration between AI assistants (Claude, GPT, and other MCP-compatible tools) and [FreeCAD](https://www.freecadweb.org/), allowing AI-assisted development and debugging of 3D models, macros, and workbenches. An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that enables integration between AI assistants (Claude, GPT, and other MCP-compatible tools) and [FreeCAD](https://www.freecadweb.org/), allowing AI-assisted development and debugging of 3D models, macros, and workbenches.
> Also includes standalone FreeCAD macros for common tasks. > Also includes standalone FreeCAD macros for common tasks.
@@ -13,6 +18,7 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that
- [Features](#features) - [Features](#features)
- [Requirements](#requirements) - [Requirements](#requirements)
- [For Users](#for-users) - [For Users](#for-users)
- [Quick Links](#quick-links)
- [MCP Server](#mcp-server) - [MCP Server](#mcp-server)
- [Installation](#installation) - [Installation](#installation)
- [Using pip (recommended)](#using-pip-recommended) - [Using pip (recommended)](#using-pip-recommended)
@@ -86,6 +92,14 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that
This section covers installation and usage for end users who want to use the MCP server with AI assistants or the standalone FreeCAD macros. This section covers installation and usage for end users who want to use the MCP server with AI assistants or the standalone FreeCAD macros.
### Quick Links
| Resource | Description |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| [Docker Hub](https://hub.docker.com/r/spkane/freecad-robust-mcp) | Pre-built Docker images for easy deployment |
| [PyPI](https://pypi.org/project/freecad-robust-mcp/) | Python package for pip installation |
| [GitHub Releases](https://github.com/spkane/freecad-robust-mcp-and-more/releases) | Release archives, changelogs, and standalone macro downloads |
## MCP Server ## MCP Server
> **Note**: Since this repository has more than just the MCP server in it, the Linux container and PyPi projects releases are both simply named `freecad-robust-mcp` which differs from the name of this git repository. > **Note**: Since this repository has more than just the MCP server in it, the Linux container and PyPi projects releases are both simply named `freecad-robust-mcp` which differs from the name of this git repository.
+33 -3
View File
@@ -1,8 +1,8 @@
# Docker build and run commands # Docker build and run commands
# Usage: just docker::build, just docker::build-all, etc. # Usage: just docker::build, just docker::build-all, etc.
# Default Docker image name # Default Docker image name (matches Docker Hub and PyPI package name)
image_name := "freecad-mcp" image_name := "freecad-robust-mcp"
registry := "spkane" registry := "spkane"
# Project root directory (justfile_directory() returns the main justfile's directory) # Project root directory (justfile_directory() returns the main justfile's directory)
@@ -53,7 +53,7 @@ shell:
--add-host=host.docker.internal:host-gateway \ --add-host=host.docker.internal:host-gateway \
-e FREECAD_MODE=xmlrpc \ -e FREECAD_MODE=xmlrpc \
-e FREECAD_SOCKET_HOST=host.docker.internal \ -e FREECAD_SOCKET_HOST=host.docker.internal \
--entrypoint /bin/bash \ --entrypoint /bin/sh \
{{image_name}} {{image_name}}
# Show image size and layers # Show image size and layers
@@ -67,6 +67,36 @@ clean:
docker rmi {{image_name}} 2>/dev/null || true docker rmi {{image_name}} 2>/dev/null || true
docker rmi {{registry}}/{{image_name}} 2>/dev/null || true docker rmi {{registry}}/{{image_name}} 2>/dev/null || true
# Scan Docker image for vulnerabilities (warn on all severities)
scan:
#!/usr/bin/env bash
set -euo pipefail
echo "Scanning {{image_name}} for vulnerabilities..."
echo ""
trivy image --severity HIGH,CRITICAL {{image_name}}
echo ""
echo "Scanning for MEDIUM/LOW (informational)..."
trivy image --severity MEDIUM,LOW {{image_name}} || true
# Scan Docker image with strict settings (fail on HIGH or CRITICAL)
scan-strict:
#!/usr/bin/env bash
set -euo pipefail
echo "Scanning {{image_name}} for HIGH/CRITICAL vulnerabilities..."
echo "(Build will fail if any HIGH or CRITICAL CVEs are found)"
echo ""
trivy image --severity HIGH,CRITICAL --exit-code 1 {{image_name}}
echo ""
echo "✓ No HIGH or CRITICAL vulnerabilities found!"
echo ""
echo "Scanning for MEDIUM/LOW (informational only)..."
trivy image --severity MEDIUM,LOW --exit-code 0 {{image_name}} || true
# Scan Docker image and output SARIF report
scan-sarif output="trivy-results.sarif":
trivy image --format sarif --output {{output}} {{image_name}}
@echo "SARIF report written to {{output}}"
# Create and configure buildx builder for multi-arch builds # Create and configure buildx builder for multi-arch builds
setup-buildx: setup-buildx:
#!/usr/bin/env bash #!/usr/bin/env bash