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:
@@ -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
|
||||||
|
|||||||
@@ -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)"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# FreeCAD Tools and MCP Server
|
# FreeCAD Tools and MCP Server
|
||||||
|
|
||||||
|
[](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/test.yaml)
|
||||||
|
[](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/docker.yaml)
|
||||||
|
[](https://github.com/spkane/freecad-robust-mcp-and-more/actions/workflows/pre-commit.yaml)
|
||||||
|
[](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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user