2025-10-31 09:09:46 -04:00
# CICD Base Setup - System dependencies and language runtimes only
2026-07-22 17:38:34 -04:00
ARG PLAYWRIGHT_VERSION = 1 .61.1
ARG PLAYWRIGHT_DISTRO = jammy
ARG PLAYWRIGHT_BROWSERS_IMAGE = mcr.microsoft.com/playwright:v${ PLAYWRIGHT_VERSION } -${ PLAYWRIGHT_DISTRO }
2026-07-22 19:26:18 -04:00
ARG UBUNTU_VERSION = 26 .04
2026-06-18 11:19:24 -04:00
FROM ${PLAYWRIGHT_BROWSERS_IMAGE } AS playwright-browsers
2026-07-22 17:38:34 -04:00
FROM ubuntu:${UBUNTU_VERSION }
ARG PLAYWRIGHT_VERSION
ARG PYTHON_VERSION = 3 .14
ARG NODE_MAJOR = 24
ARG TYPESCRIPT_VERSION = 5 .3.3
ARG ESLINT_VERSION = 9 .33.0
ARG PRETTIER_VERSION = 3 .6.2
ARG VITE_VERSION = 7 .1.10
ARG NODE_TYPES_VERSION = 20 .16.0
2025-10-31 09:09:46 -04:00
# Build args for cache busting (base dependencies change rarely)
ARG BASE_IMAGE_VERSION = v1.0.0
2026-06-18 11:19:24 -04:00
ARG BASE_IMAGE_HASH = unknown
2025-10-31 09:09:46 -04:00
ENV BASE_IMAGE_VERSION = ${ BASE_IMAGE_VERSION }
2026-06-18 11:19:24 -04:00
LABEL darkhelm.cicd-base.hash= ${ BASE_IMAGE_HASH }
LABEL org.opencontainers.image.version= ${ BASE_IMAGE_VERSION }
2025-10-31 09:09:46 -04:00
# Set timezone and make installs non-interactive
ENV DEBIAN_FRONTEND = noninteractive
ENV TZ = America/New_York
# Configure timezone
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# Configure apt timeouts and retries
RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80retries && \
echo 'Acquire::http::Timeout "60";' >> /etc/apt/apt.conf.d/80retries && \
echo 'Acquire::https::Timeout "60";' >> /etc/apt/apt.conf.d/80retries && \
echo 'Acquire::ftp::Timeout "60";' >> /etc/apt/apt.conf.d/80retries
2026-07-16 07:40:11 -04:00
# Bootstrap certificates over HTTP, then enforce HTTPS for all remaining package operations.
RUN set -eux; \
apt-get clean; \
rm -rf /var/lib/apt/lists/*; \
find /etc/apt -type f \( -name 'sources.list' -o -name '*.sources' -o -name '*.list' \) -print0 \
| xargs -0 sed -i 's|https://ports.ubuntu.com/ubuntu-ports|http://ports.ubuntu.com/ubuntu-ports|g; s|https://archive.ubuntu.com/ubuntu|http://archive.ubuntu.com/ubuntu|g; s|https://security.ubuntu.com/ubuntu|http://security.ubuntu.com/ubuntu|g' ; \
for i in 1 2 3; do \
echo "Attempt $i : Bootstrapping CA certificates..." ; \
if apt-get update && apt-get install -y --no-install-recommends ca-certificates; then \
break; \
fi ; \
if [ " $i " -lt 3 ] ; then \
echo "Bootstrap attempt $i failed, retrying..." ; \
sleep 10; \
else \
exit 1; \
fi ; \
done ; \
update-ca-certificates; \
find /etc/apt -type f \( -name 'sources.list' -o -name '*.sources' -o -name '*.list' \) -print0 \
| xargs -0 sed -i 's|http://ports.ubuntu.com/ubuntu-ports|https://ports.ubuntu.com/ubuntu-ports|g; s|http://archive.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|g; s|http://security.ubuntu.com/ubuntu|https://security.ubuntu.com/ubuntu|g' ; \
rm -rf /var/lib/apt/lists/*; \
for i in 1 2 3; do \
echo "Attempt $i : Updating package lists over HTTPS..." ; \
if apt-get update; then \
break; \
fi ; \
if [ " $i " -lt 3 ] ; then \
echo "HTTPS update attempt $i failed, retrying..." ; \
sleep 10; \
else \
exit 1; \
fi ; \
done ; \
2026-07-22 07:52:12 -04:00
if apt-cache show libasound2t64 >/dev/null 2>& 1; then \
asound_package = libasound2t64; \
else \
asound_package = libasound2; \
fi ; \
2026-07-16 07:40:11 -04:00
apt-get install -y --no-install-recommends \
2025-10-31 09:09:46 -04:00
git \
curl \
ca-certificates \
software-properties-common \
build-essential \
openssh-client \
2026-06-18 11:19:24 -04:00
# Playwright Chromium runtime dependencies (Linux)
libnspr4 \
libnss3 \
libatk1.0-0 \
libatspi2.0-0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libxkbcommon0 \
2026-07-22 07:52:12 -04:00
" $asound_package " \
2025-10-31 09:09:46 -04:00
tzdata \
2026-07-16 07:40:11 -04:00
gnupg \
wget \
2025-10-31 09:09:46 -04:00
&& rm -rf /var/lib/apt/lists/*
2026-07-22 17:38:34 -04:00
# Install Python with retry and fallback mechanisms
2025-10-31 09:09:46 -04:00
RUN for i in 1 2 3; do \
2026-07-22 17:38:34 -04:00
echo "Attempt $i : Adding deadsnakes PPA..." && \
2025-10-31 09:09:46 -04:00
add-apt-repository -y ppa:deadsnakes/ppa && \
2026-07-16 07:40:11 -04:00
find /etc/apt -type f \( -name 'sources.list' -o -name '*.sources' -o -name '*.list' \) -print0 | xargs -0 sed -i 's|http://|https://|g' && \
2025-10-31 09:09:46 -04:00
apt-get update && \
break || \
( echo "Attempt $i failed, retrying in 10s..." && sleep 10) ; \
done
RUN for i in 1 2 3; do \
2026-07-22 17:38:34 -04:00
echo "Attempt $i : Installing Python ${ PYTHON_VERSION } ..." && \
2026-07-16 07:40:11 -04:00
timeout 300 apt-get install -y --no-install-recommends \
2026-07-22 17:38:34 -04:00
python${ PYTHON_VERSION } \
python${ PYTHON_VERSION } -venv \
python${ PYTHON_VERSION } -dev && \
2025-10-31 09:09:46 -04:00
break || \
( echo "Attempt $i failed, retrying in 15s..." && sleep 15) ; \
done && \
rm -rf /var/lib/apt/lists/*
2026-07-22 17:38:34 -04:00
# Install Node.js with retry mechanism
2026-07-22 23:06:48 -04:00
RUN node_arch = " $( dpkg --print-architecture) " ; \
case " $node_arch " in \
2026-07-22 23:39:45 -04:00
amd64) node_tarball_arch = linux-x64 ;; \
arm64) node_tarball_arch = linux-arm64 ;; \
*) echo "unsupported architecture for Node.js tarball: $node_arch " >& 2; exit 1 ;; \
2026-07-22 23:06:48 -04:00
esac ; \
for i in 1 2 3; do \
echo "Attempt $i : Installing Node.js ${ NODE_MAJOR } ..." ; \
node_version = " $( python3 -c "import json, urllib.request; versions = json.load(urllib.request.urlopen('https://nodejs.org/dist/index.json')); print(next(item['version'].lstrip('v') for item in versions if item['version'].startswith('v ${ NODE_MAJOR } .')))" ) " ; \
if curl -fsSLO "https://nodejs.org/dist/v ${ node_version } /node-v ${ node_version } - ${ node_tarball_arch } .tar.xz" && \
tar -xJf "node-v ${ node_version } - ${ node_tarball_arch } .tar.xz" -C /usr/local --strip-components= 1 && \
rm -f "node-v ${ node_version } - ${ node_tarball_arch } .tar.xz" ; then \
break; \
fi ; \
if [ " $i " -lt 3 ] ; then \
echo "Attempt $i failed, retrying in 15s..." ; \
sleep 15; \
else \
exit 1; \
fi ; \
done
2025-10-31 09:09:46 -04:00
# Enable corepack for yarn and set up Yarn Berry
RUN corepack enable \
&& corepack prepare yarn@stable --activate \
&& yarn set version berry
# Configure Yarn globally for CI performance
RUN yarn config set httpTimeout 60000 && \
yarn config set enableGlobalCache false && \
yarn config set compressionLevel 0 && \
yarn config set nmMode hardlinks-local
2026-06-18 11:19:24 -04:00
# Install uv package manager globally without requiring GHCR pulls
RUN set -eu && \
curl -LsSf https://astral.sh/uv/install.sh -o /tmp/uv-install.sh && \
sh /tmp/uv-install.sh && \
rm -f /tmp/uv-install.sh && \
cp /root/.local/bin/uv /usr/local/bin/uv && \
chmod +x /usr/local/bin/uv && \
uv --version
2025-10-31 09:09:46 -04:00
2025-10-31 12:48:40 -04:00
# Install common development tools globally using npm (more reliable for global installs)
2025-10-31 11:25:34 -04:00
RUN echo "=== Installing Global Development Tools ===" && \
2025-10-31 12:48:40 -04:00
# Use npm for global installations (works better than yarn global in Berry)
npm install -g \
2026-07-22 17:38:34 -04:00
@playwright/test@${ PLAYWRIGHT_VERSION } \
typescript@${ TYPESCRIPT_VERSION } \
eslint@${ ESLINT_VERSION } \
prettier@${ PRETTIER_VERSION } \
vite@${ VITE_VERSION } \
@types/node@${ NODE_TYPES_VERSION } && \
2025-10-31 12:48:40 -04:00
# Verify global tools are available
which playwright && \
which tsc && \
which eslint && \
which prettier && \
echo "✓ Global Node.js development tools installed via npm"
2025-10-31 11:25:34 -04:00
2026-06-18 11:19:24 -04:00
# Keep browser binaries in a deterministic location so E2E runs do not re-download them.
ENV PLAYWRIGHT_BROWSERS_PATH = /ms-playwright
# Copy preinstalled browsers from the official Playwright image to avoid flaky CDN downloads.
COPY --from= playwright-browsers /ms-playwright /ms-playwright
RUN echo "=== Verifying preinstalled Playwright Chromium Browser ===" && \
chromium_count = $( find " ${ PLAYWRIGHT_BROWSERS_PATH } " -maxdepth 1 -type d -name 'chromium-*' | wc -l) && \
echo "playwright_chromium_dir_count= ${ chromium_count } path= ${ PLAYWRIGHT_BROWSERS_PATH } " && \
if [ " ${ chromium_count } " -eq 0 ] ; then \
echo "❌ No Chromium browser directories found in preinstalled Playwright cache" ; \
exit 1; \
fi && \
ls -la " ${ PLAYWRIGHT_BROWSERS_PATH } " && \
echo "✓ Playwright Chromium preinstalled at ${ PLAYWRIGHT_BROWSERS_PATH } "
2025-10-31 11:25:34 -04:00
# Pre-install common Python development dependencies globally
# These are stable tools that rarely change and take time to compile
RUN echo "=== Pre-installing Common Python Dev Tools ===" && \
# Create a global uv environment with common dev tools
uv venv /opt/python-dev-tools && \
2025-10-31 13:25:03 -04:00
# Use system uv to install packages into the virtual environment
uv pip install --python /opt/python-dev-tools/bin/python \
2025-10-31 11:25:34 -04:00
ruff>= 0.6.0 \
pyright>= 1.1.380 \
pytest>= 7.4.0 \
pytest-asyncio>= 0.21.0 \
pytest-cov>= 4.1.0 \
pytest-mock>= 3.12.0 \
pre-commit>= 3.0.0 \
pyyaml>= 6.0 \
yamllint>= 1.35.0 \
toml-sort>= 0.23.0 \
xdoctest>= 1.1.0 \
2025-10-31 13:25:03 -04:00
language-formatters-pre-commit-hooks>= 2.14.0 \
poethepoet>= 0.24.0 && \
2025-10-31 11:25:34 -04:00
echo "✓ Common Python dev tools pre-installed in /opt/python-dev-tools"
2025-10-31 09:09:46 -04:00
# Create a script to set up SSH for git operations (using secrets mount)
RUN echo '#!/bin/bash' > /usr/local/bin/setup-ssh && \
echo 'if [ -f /run/secrets/ssh_private_key ]; then' >> /usr/local/bin/setup-ssh && \
echo ' mkdir -p ~/.ssh' >> /usr/local/bin/setup-ssh && \
echo ' cp /run/secrets/ssh_private_key ~/.ssh/id_rsa' >> /usr/local/bin/setup-ssh && \
echo ' chmod 600 ~/.ssh/id_rsa' >> /usr/local/bin/setup-ssh && \
echo ' ssh-keyscan -H github.com >> ~/.ssh/known_hosts 2>/dev/null' >> /usr/local/bin/setup-ssh && \
2026-06-18 11:19:24 -04:00
echo ' ssh-keyscan -p 2222 -H kankali.darkhelm.lan >> ~/.ssh/known_hosts 2>/dev/null' >> /usr/local/bin/setup-ssh && \
2025-10-31 09:09:46 -04:00
echo 'else' >> /usr/local/bin/setup-ssh && \
echo ' echo "No SSH key provided via secrets mount"' >> /usr/local/bin/setup-ssh && \
echo 'fi' >> /usr/local/bin/setup-ssh && \
chmod +x /usr/local/bin/setup-ssh
# Verify that all base tools are working
RUN echo "=== Base System Verification ===" && \
2026-07-22 17:38:34 -04:00
python${ PYTHON_VERSION } --version && \
2025-10-31 09:09:46 -04:00
node --version && \
yarn --version && \
uv --version && \
git --version && \
2025-10-31 11:25:34 -04:00
echo "=== Global Development Tools Verification ===" && \
2025-10-31 12:48:40 -04:00
playwright --version && \
tsc --version && \
eslint --version && \
prettier --version && \
2025-10-31 11:25:34 -04:00
/opt/python-dev-tools/bin/ruff --version && \
/opt/python-dev-tools/bin/pyright --version && \
/opt/python-dev-tools/bin/pytest --version && \
/opt/python-dev-tools/bin/pre-commit --version && \
2025-10-31 13:25:03 -04:00
/opt/python-dev-tools/bin/poe --version && \
2025-10-31 11:25:34 -04:00
echo "✓ All base system and development tools verified successfully"
2025-10-31 09:09:46 -04:00
# Set working directory
WORKDIR /workspace
# Default to bash
SHELL [ "/bin/bash" , "-c" ]
CMD [ "/bin/bash" ]