Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Local development values. ./deploy.sh dev copies this file to .env, which compose reads.
# Production never sees this file: its deploy writes .env from the repository secrets.
SECRET_KEY=CHANGE_ME_min_32_chars_long_!!!!
MONGO_USER=root
MONGO_PASSWORD=rootpassword
REDIS_PASSWORD=redispassword
MONGO_MONITOR_PASSWORD=monitorpassword
ADMIN_USER_PASSWORD=admin123
DEFAULT_USER_PASSWORD=user123
GRAFANA_ADMIN_PASSWORD=admin123
31 changes: 3 additions & 28 deletions .github/actions/e2e-boot/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ inputs:
github-token:
description: 'GitHub token for GHCR authentication'
required: true
install-kueue:
description: 'Pre-pull the Kueue controller image, for jobs that install Kueue (default: true)'
required: false
default: 'true'

runs:
using: 'composite'
Expand All @@ -28,11 +24,12 @@ runs:
env:
IMAGE_TAG: ${{ inputs.image-tag }}
run: |
cp .env.example .env
nohup bash -c '
IMAGE_TAG='"$IMAGE_TAG"' docker compose pull --quiet 2>&1
echo "--- pull done, starting infra ---"
docker compose up -d --no-build \
mongo redis shared-ca kafka 2>&1
mongo redis kafka 2>&1
echo $? > /tmp/infra-pull.exit
' > /tmp/infra-pull.log 2>&1 &
echo $! > /tmp/infra-pull.pid
Expand All @@ -46,35 +43,13 @@ runs:
-v /etc/rancher/k3s:/etc/rancher/k3s -e K3S_KUBECONFIG_MODE=644 \
"$K3S_IMAGE" server --disable=traefik --disable-network-policy --tls-san host.docker.internal

- name: Pre-pull executor images and Kueue manifest (background)
- name: Pre-pull executor images (background)
shell: bash
run: |
KUEUE_VERSION="${KUEUE_VERSION:-v0.20.1}"

nohup bash -c '
curl --proto =https --tlsv1.2 -fsSL -o /tmp/kueue-manifests.yaml \
"https://github.com/kubernetes-sigs/kueue/releases/download/'"$KUEUE_VERSION"'/manifests.yaml"
echo $? > /tmp/kueue-download.exit
' > /tmp/kueue-download.log 2>&1 &
echo $! > /tmp/kueue-download.pid

# No docker exec before k3s writes its kubeconfig: one during its root cgroup evacuation kills the kubelet
nohup bash -c '
timeout 120 bash -c "until [ -s /etc/rancher/k3s/k3s.yaml ] && docker exec k3s crictl info >/dev/null 2>&1; do sleep 2; done"
docker exec k3s crictl pull docker.io/library/python:3.14-slim
echo $? > /tmp/crictl-python.exit
' > /tmp/crictl-python.log 2>&1 &
echo $! > /tmp/crictl-python.pid

# registry.k8s.io can take minutes for this image, longer than e2e-ready's wait for the controller
- name: Pre-pull the Kueue controller image (background)
if: inputs.install-kueue == 'true'
shell: bash
run: |
KUEUE_VERSION="${KUEUE_VERSION:-v0.20.1}"
nohup bash -c '
timeout 120 bash -c "until [ -s /etc/rancher/k3s/k3s.yaml ] && docker exec k3s crictl info >/dev/null 2>&1; do sleep 2; done"
docker exec k3s crictl pull registry.k8s.io/kueue/kueue:'"$KUEUE_VERSION"'
echo $? > /tmp/crictl-kueue.exit
' > /tmp/crictl-kueue.log 2>&1 &
echo $! > /tmp/crictl-kueue.pid
67 changes: 1 addition & 66 deletions .github/actions/e2e-ready/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ inputs:
description: 'Also wait for frontend health check (default: false)'
required: false
default: 'false'
install-kueue:
description: 'Install Kueue CRDs for executor pods (default: true)'
required: false
default: 'true'

runs:
using: 'composite'
Expand All @@ -27,72 +23,11 @@ runs:
export KUBECONFIG=/home/runner/.kube/config
# kubectl wait fails at once while the node isn't registered yet, so retry until it is
timeout 120 bash -c 'until kubectl wait --for=condition=Ready node --all --timeout=5s >/dev/null 2>&1; do sleep 2; done'
kubectl create namespace integr8scode --dry-run=client -o yaml | kubectl apply -f -
sed -E 's#https://(127\.0\.0\.1|0\.0\.0\.0):6443#https://host.docker.internal:6443#g' \
/home/runner/.kube/config > backend/kubeconfig.yaml
chmod 644 backend/kubeconfig.yaml

- name: Install Kueue and wait for executor image pulls
if: inputs.install-kueue == 'true'
- name: Wait for the executor image pull
shell: bash
run: |
set -e

KUEUE_VERSION="${KUEUE_VERSION:-v0.20.1}"
KUEUE_MANIFEST_SHA256="${KUEUE_MANIFEST_SHA256:-c48514295365aae0c7dc578c70330befdd0015fec57cb4a06ba872bbef8750dd}"
KUEUE_MANIFEST="/tmp/kueue-manifests.yaml"

timeout 120 tail --pid="$(cat /tmp/kueue-download.pid)" -f /dev/null 2>/dev/null || true
cat /tmp/kueue-download.log 2>/dev/null || true
[ "$(cat /tmp/kueue-download.exit)" = "0" ]

echo "${KUEUE_MANIFEST_SHA256} ${KUEUE_MANIFEST}" | sha256sum -c -
timeout 600 tail --pid="$(cat /tmp/crictl-kueue.pid)" -f /dev/null 2>/dev/null || true
cat /tmp/crictl-kueue.log 2>/dev/null || true
[ "$(cat /tmp/crictl-kueue.exit)" = "0" ]

kubectl apply --server-side -f "$KUEUE_MANIFEST"
rm -f "$KUEUE_MANIFEST"
# Kueue's cert bootstrap answers /readyz with 404 until the kubelet mounts its new secret, which can take minutes
if ! kubectl wait --for=condition=Available --timeout=300s deployment/kueue-controller-manager -n kueue-system; then
kubectl -n kueue-system get pods -o wide
kubectl -n kueue-system describe pods
kubectl -n kueue-system get events --sort-by=.lastTimestamp
kubectl -n kueue-system logs deployment/kueue-controller-manager --all-containers --tail=300
exit 1
fi

kubectl apply --server-side -f - <<'EOF'
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: default-flavor
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: executor-queue
spec:
namespaceSelector: {}
resourceGroups:
- coveredResources: ["cpu", "memory"]
flavors:
- name: default-flavor
resources:
- name: cpu
nominalQuota: "32"
- name: memory
nominalQuota: "4Gi"
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: executor-queue
namespace: integr8scode
spec:
clusterQueue: executor-queue
EOF

timeout 120 tail --pid="$(cat /tmp/crictl-python.pid)" -f /dev/null 2>/dev/null || true
cat /tmp/crictl-python.log 2>/dev/null || true
[ "$(cat /tmp/crictl-python.exit)" = "0" ]
Expand Down
3 changes: 1 addition & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ updates:
- dependency-name: "typescript"
versions: [">=7.0.0"]

# Docker base images, one entry so one group covers all three Dockerfiles
# Docker base images, one entry so one group covers both Dockerfiles
- package-ecosystem: "docker"
directories:
- "/backend"
- "/frontend"
- "/cert-generator"
schedule:
interval: "weekly"
commit-message:
Expand Down
2 changes: 1 addition & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
- docker-compose.yaml
- deploy.sh
- "**/Dockerfile*"
- cert-generator/**
- .env.example
- .github/workflows/docker.yml
- .github/workflows/release-deploy.yml

Expand Down
30 changes: 7 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ concurrency:
env:
REGISTRY: ghcr.io
K3S_IMAGE: rancher/k3s:v1.37.1-k3s1@sha256:ca7f37d993d82ef0dcdcfecb2e0e2618ea541dbaffc620c8cedebe01a82acd0d
KUEUE_VERSION: v0.20.1
KUEUE_MANIFEST_SHA256: c48514295365aae0c7dc578c70330befdd0015fec57cb4a06ba872bbef8750dd

jobs:
changes:
Expand Down Expand Up @@ -52,7 +50,7 @@ jobs:
- 'frontend/**'
infra:
- *workflow
- 'cert-generator/**'
- '.env.example'
- 'docker-compose.yaml'
- 'deploy.sh'
- '.github/actions/**'
Expand Down Expand Up @@ -432,7 +430,7 @@ jobs:
id: base-cache
with:
path: /tmp/base-image.tar.zst
key: base-image-${{ runner.os }}-${{ hashFiles('backend/Dockerfile.base', 'backend/pyproject.toml', 'backend/uv.lock') }}
key: base-image-${{ runner.os }}-${{ hashFiles('backend/Dockerfile', 'backend/pyproject.toml', 'backend/uv.lock') }}

- name: Load base image from cache
if: steps.base-cache.outputs.cache-hit == 'true'
Expand All @@ -443,7 +441,8 @@ jobs:
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: ./backend
file: ./backend/Dockerfile.base
file: ./backend/Dockerfile
target: base
load: true
tags: integr8scode-base:latest
cache-from: type=gha,scope=backend-base
Expand All @@ -453,24 +452,13 @@ jobs:
if: steps.base-cache.outputs.cache-hit != 'true'
run: docker save integr8scode-base:latest | zstd -T0 -3 > /tmp/base-image.tar.zst

# ── Backend (depends on local base image) ───────────────
# ── Backend (the cached base image stands in for the Dockerfile's base stage) ───────────────
- name: Build backend image
run: |
docker build -t integr8scode-backend:latest \
--build-context base=docker-image://integr8scode-base:latest \
-f ./backend/Dockerfile ./backend

# ── Utility images (GHA-cached, independent of base) ────────────
- name: Build cert-generator image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: ./cert-generator
file: ./cert-generator/Dockerfile
load: true
tags: integr8scode-cert-generator:latest
cache-from: type=gha,scope=cert-generator
cache-to: type=gha,mode=max,scope=cert-generator

# ── Frontend (nginx + SSL) ─────────────────────────────────────
- name: Build frontend image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
Expand All @@ -490,14 +478,12 @@ jobs:
IMG: ${{ env.REGISTRY }}/${{ steps.tags.outputs.image-prefix }}
run: |
# Tag all images for GHCR
docker tag integr8scode-base:latest "$IMG/base:$TAG"
docker tag integr8scode-backend:latest "$IMG/backend:$TAG"
docker tag integr8scode-cert-generator:latest "$IMG/cert-generator:$TAG"
docker tag integr8scode-frontend:latest "$IMG/frontend:$TAG"

# Push all 4 images in parallel, tracking each PID
# Push both images in parallel, tracking each PID
declare -A PIDS
for name in base backend cert-generator frontend; do
for name in backend frontend; do
docker push "$IMG/$name:$TAG" &
PIDS[$name]=$!
done
Expand Down Expand Up @@ -606,7 +592,6 @@ jobs:
with:
image-tag: ${{ needs.build-images.outputs.sha-tag }}
github-token: ${{ secrets.GITHUB_TOKEN }}
install-kueue: 'false'

# Phase 2: Node + Playwright setup (overlaps with k3s boot + image pull)
- name: Setup Node.js
Expand Down Expand Up @@ -642,7 +627,6 @@ jobs:
with:
image-tag: ${{ needs.build-images.outputs.sha-tag }}
wait-for-frontend: 'true'
install-kueue: 'false'

- name: Run Playwright tests
timeout-minutes: 10
Expand Down
7 changes: 1 addition & 6 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ jobs:
fail-fast: false
matrix:
image:
- base
- backend
- frontend
- cert-generator
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Expand Down Expand Up @@ -108,10 +106,8 @@ jobs:
echo "Promoting tag: $TAG → latest"
echo ""

crane copy "$REGISTRY/$PREFIX/base:$TAG" "$REGISTRY/$PREFIX/base:latest"
crane copy "$REGISTRY/$PREFIX/backend:$TAG" "$REGISTRY/$PREFIX/backend:latest"
crane copy "$REGISTRY/$PREFIX/frontend:$TAG" "$REGISTRY/$PREFIX/frontend:latest"
crane copy "$REGISTRY/$PREFIX/cert-generator:$TAG" "$REGISTRY/$PREFIX/cert-generator:latest"

summary:
name: Summary
Expand All @@ -138,9 +134,8 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Image | Pull Command |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------------|" >> $GITHUB_STEP_SUMMARY
echo "| Base | \`docker pull $REGISTRY/$PREFIX/base:latest\` |" >> $GITHUB_STEP_SUMMARY
echo "| Backend | \`docker pull $REGISTRY/$PREFIX/backend:latest\` |" >> $GITHUB_STEP_SUMMARY
echo "| Frontend | \`docker pull $REGISTRY/$PREFIX/frontend:latest\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Security Scans" >> $GITHUB_STEP_SUMMARY
echo "All 4 images scanned with Trivy (CRITICAL + HIGH, unfixed ignored)." >> $GITHUB_STEP_SUMMARY
echo "Both images scanned with Trivy (CRITICAL + HIGH, unfixed ignored)." >> $GITHUB_STEP_SUMMARY
49 changes: 23 additions & 26 deletions .github/workflows/release-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ jobs:
PREFIX="${GITHUB_REPOSITORY_OWNER,,}/integr8scode"
VERSION="${{ steps.version.outputs.version }}"
TAG="sha-${{ steps.sha.outputs.short-sha }}"
for img in base backend frontend cert-generator; do
for img in backend frontend; do
crane copy "$REGISTRY/$PREFIX/$img:$TAG" "$REGISTRY/$PREFIX/$img:$VERSION"
done

Expand Down Expand Up @@ -159,14 +159,6 @@ jobs:
command_timeout: 10m
script: |
set -e
# compose falls back to public defaults for these, so a missing secret must stop the deploy
: "${SECRET_KEY:?the JWT_SECRET_KEY secret is empty}"
: "${MONGO_USER:?the MONGO_USER secret is empty}"
: "${MONGO_PASSWORD:?the MONGO_PASSWORD secret is empty}"
: "${MONGO_MONITOR_PASSWORD:?the MONGO_MONITOR_PASSWORD secret is empty}"
: "${REDIS_PASSWORD:?the REDIS_PASSWORD secret is empty}"
: "${ADMIN_USER_PASSWORD:?the ADMIN_USER_PASSWORD secret is empty}"
: "${DEFAULT_USER_PASSWORD:?the DEFAULT_USER_PASSWORD secret is empty}"
cd /opt/Integr8sCode

# Images the running stack doesn't use; old tags can be pulled from GHCR again
Expand All @@ -176,23 +168,28 @@ jobs:
git fetch --tags --force origin
git checkout --detach "$IMAGE_TAG"

export IMAGE_TAG="$IMAGE_TAG"
export COMPOSE_PROFILES=observability
export SECRET_KEY="$SECRET_KEY"
export MONGO_USER="$MONGO_USER"
export MONGO_PASSWORD="$MONGO_PASSWORD"
export MONGO_MONITOR_PASSWORD="$MONGO_MONITOR_PASSWORD"
export REDIS_PASSWORD="$REDIS_PASSWORD"
export ADMIN_USER_PASSWORD="$ADMIN_USER_PASSWORD"
export DEFAULT_USER_PASSWORD="$DEFAULT_USER_PASSWORD"
export GRAFANA_ROOT_URL="https://grafana.integr8scode.cc/"
export GRAFANA_ADMIN_USER="$GRAFANA_ADMIN_USER"
export GRAFANA_ADMIN_PASSWORD="$GRAFANA_ADMIN_PASSWORD"
export MAILJET_API_KEY="$MAILJET_API_KEY"
export MAILJET_SECRET_KEY="$MAILJET_SECRET_KEY"
export MAILJET_FROM_ADDRESS="$MAILJET_FROM_ADDRESS"
export GF_SMTP_HOST="$MAILJET_HOST"
export GRAFANA_ALERT_RECIPIENTS="$GRAFANA_ALERT_RECIPIENTS"
# compose reads .env for every command, so the backup cron and commands run by hand get the same values; it refuses to start on an empty secret
umask 077
cat > .env <<EOF
IMAGE_TAG='$IMAGE_TAG'
COMPOSE_PROFILES=observability
SECRET_KEY='$SECRET_KEY'
MONGO_USER='$MONGO_USER'
MONGO_PASSWORD='$MONGO_PASSWORD'
MONGO_MONITOR_PASSWORD='$MONGO_MONITOR_PASSWORD'
REDIS_PASSWORD='$REDIS_PASSWORD'
ADMIN_USER_PASSWORD='$ADMIN_USER_PASSWORD'
DEFAULT_USER_PASSWORD='$DEFAULT_USER_PASSWORD'
GRAFANA_ROOT_URL='https://grafana.integr8scode.cc/'
GRAFANA_ADMIN_USER='$GRAFANA_ADMIN_USER'
GRAFANA_ADMIN_PASSWORD='$GRAFANA_ADMIN_PASSWORD'
MAILJET_API_KEY='$MAILJET_API_KEY'
MAILJET_SECRET_KEY='$MAILJET_SECRET_KEY'
MAILJET_FROM_ADDRESS='$MAILJET_FROM_ADDRESS'
GF_SMTP_HOST='$MAILJET_HOST'
GRAFANA_ALERT_RECIPIENTS='$GRAFANA_ALERT_RECIPIENTS'
EOF

docker compose pull
docker compose up -d --remove-orphans --no-build --wait --wait-timeout 180

Expand Down
Loading
Loading