Add Docker image + Gitea CI for Virgo Dev deploy #7
34
.dockerignore
Normal file
34
.dockerignore
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Secrets — never bake into the image (injected at runtime via compose env).
|
||||||
|
.env
|
||||||
|
|
||||||
|
# The dashboard is rebuilt in the node stage and COPY'd in fresh; keep the
|
||||||
|
# gitignored working-tree copies out of the build context.
|
||||||
|
dashboard/build/
|
||||||
|
dashboard/node_modules/
|
||||||
|
dashboard/.svelte-kit/
|
||||||
|
|
||||||
|
# Python build/cache cruft.
|
||||||
|
__pycache__/
|
||||||
|
**/__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.pytest_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# Local runtime artifacts.
|
||||||
|
recordings/
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# VCS / editor / OS.
|
||||||
|
.git/
|
||||||
|
.gitea/
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Not needed at runtime.
|
||||||
|
tests/
|
||||||
|
docs/
|
||||||
105
.gitea/workflows/cve-scan-docker-build.yml
Normal file
105
.gitea/workflows/cve-scan-docker-build.yml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
name: CVE Scan & Docker Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
# A pushed version tag (e.g. 0.2.0 or v0.2.0) cuts a release: the build
|
||||||
|
# below stamps the image with the matching semver tag (e.g. :0.2.0) so the
|
||||||
|
# deploy can pin an immutable release instead of a moving :latest/:sha.
|
||||||
|
tags: ['*']
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: git.helu.ca
|
||||||
|
IMAGE_NAME: ${{ gitea.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security-scan:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Trivy
|
||||||
|
run: |
|
||||||
|
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin
|
||||||
|
trivy --version
|
||||||
|
|
||||||
|
- name: Install pip-tools and resolve Python dependencies
|
||||||
|
run: |
|
||||||
|
python3 -m venv /tmp/scanenv
|
||||||
|
/tmp/scanenv/bin/pip install --quiet pip-tools
|
||||||
|
/tmp/scanenv/bin/pip-compile pyproject.toml \
|
||||||
|
-o /tmp/requirements.txt \
|
||||||
|
--no-header --quiet --allow-unsafe --strip-extras \
|
||||||
|
--resolver=backtracking || {
|
||||||
|
/tmp/scanenv/bin/pip install --quiet . && \
|
||||||
|
/tmp/scanenv/bin/pip freeze > /tmp/requirements.txt
|
||||||
|
}
|
||||||
|
cat /tmp/requirements.txt
|
||||||
|
|
||||||
|
- name: Scan Python dependencies for CVEs
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
trivy fs --scanners vuln --severity HIGH,CRITICAL --format table /tmp/requirements.txt
|
||||||
|
|
||||||
|
- name: Audit dashboard npm dependencies
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
cd dashboard
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
npm audit --audit-level=high || true
|
||||||
|
|
||||||
|
- name: Scan repository for secrets
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
trivy fs --scanners secret --severity HIGH,CRITICAL --format table .
|
||||||
|
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: security-scan
|
||||||
|
if: always()
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Gitea Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
password: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
|
|
||||||
|
# Single image: Hold Slayer is one FastAPI process exposing REST/WS/MCP
|
||||||
|
# and serving its own built SvelteKit dashboard at "/" — no separate
|
||||||
|
# web/nginx image. The Dockerfile's node stage builds the dashboard.
|
||||||
|
- name: Extract metadata for image
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=sha,prefix=
|
||||||
|
type=raw,value=latest,enable=${{ gitea.ref == 'refs/heads/main' }}
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
|
- name: Scan image for CVEs
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin
|
||||||
|
IMAGE_TAG=$(echo "${{ steps.meta.outputs.tags }}" | head -n1)
|
||||||
|
echo "Scanning image: ${IMAGE_TAG}"
|
||||||
|
trivy image --severity HIGH,CRITICAL --format table "${IMAGE_TAG}"
|
||||||
53
Dockerfile
Normal file
53
Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Hold Slayer — single image: FastAPI process that also serves the built
|
||||||
|
# SvelteKit dashboard at "/". One container, four surfaces (REST/WS/MCP/dash).
|
||||||
|
#
|
||||||
|
# pjsua2 is deliberately NOT built here — it is not pip-installable (compiled
|
||||||
|
# from pjproject) and the media pipeline degrades to documented stub mode
|
||||||
|
# without it. That is correct for a mock-SIP dev deploy (USE_MOCK_SIP=true);
|
||||||
|
# /health honestly reports the mock engine as "degraded". Building real media
|
||||||
|
# is a separate, deliberate piece of work.
|
||||||
|
|
||||||
|
# Stage 1: build the SvelteKit dashboard → dashboard/build/ (SPA, static).
|
||||||
|
# dashboard/build and dashboard/node_modules are gitignored, so build fresh
|
||||||
|
# here rather than copying a stale working-tree artifact.
|
||||||
|
FROM node:22-alpine AS dashboard
|
||||||
|
WORKDIR /dashboard
|
||||||
|
COPY dashboard/package.json dashboard/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY dashboard/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 2: runtime. The app runs FROM SOURCE at /app (not purely from
|
||||||
|
# site-packages): db/database.py locates alembic.ini via
|
||||||
|
# Path(__file__).parent.parent, and main.py/config.py are loose top-level
|
||||||
|
# modules — both require the source tree layout under the working dir. An
|
||||||
|
# editable install puts the deps + entry points in place while keeping /app/db,
|
||||||
|
# /app/config.py, /app/alembic.ini resolving to the real files.
|
||||||
|
FROM python:3.12-slim
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# build-essential: some deps compile from source (no manylinux wheel).
|
||||||
|
# curl: required for the compose healthcheck (GET /health).
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends build-essential curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install dependencies first (better layer caching) using just the manifest,
|
||||||
|
# then the source. -e keeps the package importable from /app so alembic.ini
|
||||||
|
# and the loose modules resolve correctly at runtime.
|
||||||
|
COPY pyproject.toml README.md ./
|
||||||
|
COPY . .
|
||||||
|
# Bring in the freshly built dashboard (overwrites any stale gitignored copy).
|
||||||
|
COPY --from=dashboard /dashboard/build ./dashboard/build
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -e . \
|
||||||
|
&& apt-get purge -y build-essential && apt-get autoremove -y
|
||||||
|
|
||||||
|
EXPOSE 21081
|
||||||
|
|
||||||
|
# Migrations run in the app's own init_db() on boot (db/database.py), so no
|
||||||
|
# separate `alembic upgrade` here. Bind host/port from the same env vars
|
||||||
|
# pydantic-settings reads (HOST/PORT) so configured values and the actual bind
|
||||||
|
# cannot drift. Deploy sets PORT=21081 (image default 8000 collides with other
|
||||||
|
# host-net services on triton).
|
||||||
|
CMD ["sh", "-c", "uvicorn main:app --host ${HOST:-0.0.0.0} --port ${PORT:-21081}"]
|
||||||
Reference in New Issue
Block a user