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 | \
grep -q '"result"' && echo "Container test passed" || echo "Container test completed"
- name: Scan for vulnerabilities
if: github.event_name != 'pull_request'
uses: aquasecurity/trivy-action@master
- name: Cache Trivy vulnerability database
uses: actions/cache@v4
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"
output: "trivy-results.sarif"
cache-dir: .trivy-cache
continue-on-error: true
- name: Upload Trivy scan results
+43 -6
View File
@@ -164,8 +164,10 @@ jobs:
name: Publish to TestPyPI
needs: [build, test-install]
runs-on: ubuntu-latest
# Only publish prereleases to TestPyPI
if: contains(github.ref, '-')
# Only publish alpha releases to TestPyPI for early testing.
# 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:
name: testpypi
url: https://test.pypi.org/p/freecad-robust-mcp
@@ -188,8 +190,10 @@ jobs:
name: Publish to PyPI
needs: [build, test-install]
runs-on: ubuntu-latest
# Only publish stable releases to PyPI
if: ${{ !contains(github.ref, '-') }}
# Publish beta, rc, and stable releases to PyPI (not alpha).
# 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:
name: pypi
url: https://pypi.org/p/freecad-robust-mcp
@@ -245,9 +249,10 @@ jobs:
echo ""
} >> "$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 "### Install from TestPyPI"
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 "\`\`\`"
} >> "$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
{
echo "**Type:** Stable release (published to PyPI)"
+4
View File
@@ -97,6 +97,10 @@ cython_debug/
# Ruff
.ruff_cache/
# Trivy (pre-commit and CI cache)
.pre-commit-trivy-cache/
.trivy-cache/
# UV
# 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
pre-commit = "4.5" # pre-commit hooks
github-cli = "2.74" # GitHub CLI for PR/issue management
trivy = "0.62" # container vulnerability scanner
[env]
# FreeCAD connection mode:
+16
View File
@@ -221,6 +221,21 @@ repos:
- id: hadolint-docker
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
# ==========================================================================
@@ -240,4 +255,5 @@ ci:
skip:
- mypy # Needs dependencies installed
- hadolint-docker # Needs Docker
- trivyconfig-docker # Needs Docker
- trufflehog # Can be slow in CI
+24 -16
View File
@@ -3,6 +3,9 @@
# FreeCAD MCP Server Dockerfile
# 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:
# docker build -t freecad-mcp .
#
@@ -15,24 +18,22 @@
# =============================================================================
# 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
# hadolint ignore=DL3008
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install build dependencies for compiling Python packages with native extensions
# hadolint ignore=DL3018
RUN apk add --no-cache \
build-base \
libffi-dev
# Set up working directory
WORKDIR /app
# Install uv for fast dependency management
# Using pip cache mount for faster rebuilds
# Upgrade pip to fix CVE-2025-8869, then install uv for fast dependency management
# hadolint ignore=DL3013
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 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
# =============================================================================
FROM python:3.11-slim AS runtime
FROM python:3.11-alpine AS runtime
# Labels for container metadata (OCI Image Spec)
# 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.vendor="Sean P. Kane" \
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
RUN groupadd --gid 1000 mcpuser && \
useradd --uid 1000 --gid 1000 --shell /bin/bash --create-home mcpuser
# Create non-root user for security (Alpine uses addgroup/adduser)
RUN addgroup -g 1000 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 --from=builder /opt/venv /opt/venv
+14
View File
@@ -1,5 +1,10 @@
# 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.
> Also includes standalone FreeCAD macros for common tasks.
@@ -13,6 +18,7 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that
- [Features](#features)
- [Requirements](#requirements)
- [For Users](#for-users)
- [Quick Links](#quick-links)
- [MCP Server](#mcp-server)
- [Installation](#installation)
- [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.
### 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
> **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
# Usage: just docker::build, just docker::build-all, etc.
# Default Docker image name
image_name := "freecad-mcp"
# Default Docker image name (matches Docker Hub and PyPI package name)
image_name := "freecad-robust-mcp"
registry := "spkane"
# Project root directory (justfile_directory() returns the main justfile's directory)
@@ -53,7 +53,7 @@ shell:
--add-host=host.docker.internal:host-gateway \
-e FREECAD_MODE=xmlrpc \
-e FREECAD_SOCKET_HOST=host.docker.internal \
--entrypoint /bin/bash \
--entrypoint /bin/sh \
{{image_name}}
# Show image size and layers
@@ -67,6 +67,36 @@ clean:
docker rmi {{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
setup-buildx:
#!/usr/bin/env bash