name: ci on: push: branches: - "**" pull_request: workflow_dispatch: env: REGISTRY: gcr.forust.xyz jobs: lint: runs-on: [self-hosted, linux, arch, homelab] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Check formatting with Prettier shell: bash run: | mapfile -t prettier_files < <( git ls-files \ | grep -E '\.(md|json|ya?ml|html|css)$' \ | grep -Ev '^(\.docs/|\.zed/|errorpages/html/|homepages/(forust_files|xdfnx_files)/)' ) if [ "${#prettier_files[@]}" -eq 0 ]; then echo "No Prettier-managed files found." exit 0 fi docker run --rm \ -v "$PWD:/work" \ -w /work \ node:22-alpine \ sh -lc 'npx --yes prettier@3 --check --ignore-unknown "$@"' sh "${prettier_files[@]}" - name: Lint Python with Ruff shell: bash run: | docker run --rm \ -v "$PWD:/work" \ -w /work \ ghcr.io/astral-sh/ruff:latest \ check . - name: Lint YAML syntax shell: bash run: | docker run --rm \ -v "$PWD:/work" \ -w /work \ cytopia/yamllint:latest \ -c .yamllint . - name: Lint Dockerfiles shell: bash run: | mapfile -t dockerfiles < <( git ls-files ':(glob)**/Dockerfile' ':(glob)**/Dockerfile.*' ) if [ "${#dockerfiles[@]}" -eq 0 ]; then echo "No Dockerfiles found." exit 0 fi docker run --rm \ -v "$PWD:/work" \ -w /work \ --entrypoint hadolint \ hadolint/hadolint:latest-debian \ -c .hadolint.yaml "${dockerfiles[@]}" validate: runs-on: [self-hosted, linux, arch, homelab] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Validate YAML syntax shell: bash run: | docker run --rm \ -v "$PWD:/work" \ -w /work \ cytopia/yamllint:latest \ -c .yamllint . - name: Validate Kubernetes manifests shell: bash run: | mapfile -t manifests < <( git ls-files ':(glob)**/k8s/**/*.yaml' ':(glob)**/k8s/**/*.yml' \ | grep -Ev '(^|/)(kustomization\.ya?ml|.*\.example\.ya?ml|.*values\.ya?ml|patch-.*\.ya?ml)$' ) if [ "${#manifests[@]}" -eq 0 ]; then echo "No Kubernetes manifests found." exit 0 fi docker run --rm \ -v "$PWD:/work" \ -w /work \ ghcr.io/yannh/kubeconform:latest \ -strict \ -ignore-missing-schemas \ -summary \ "${manifests[@]}" build: if: github.event_name != 'pull_request' && (github.ref_name == 'main' || github.ref_name == 'dev') runs-on: [self-hosted, linux, arch, homelab] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Detect changed docker-built services id: services shell: bash run: | base="${{ github.event.before }}" if [ -z "$base" ] || [ "$base" = "0000000000000000000000000000000000000000" ]; then base="$(git rev-list --max-parents=0 HEAD)" fi mapfile -t changed_files < <(git diff --name-only "$base" "${GITHUB_SHA}") services=() add_service() { local name="$1" local seen=0 for existing in "${services[@]}"; do if [ "$existing" = "$name" ]; then seen=1 break fi done if [ "$seen" -eq 0 ]; then services+=("$name") fi } for file in "${changed_files[@]}"; do case "$file" in dtek_notif/*) add_service dtek_notif ;; errorpages/*) add_service errorpages ;; userbot/*) add_service userbot ;; homepages/*) add_service homepages ;; edu_master/phpsessid-bot/*|edu_master/webinar-checker/*|edu_master/compose.yaml) add_service edu_master ;; esac done if [ "${#services[@]}" -eq 0 ]; then echo "No docker-built services changed." echo "services=" >> "$GITHUB_OUTPUT" exit 0 fi printf '%s\n' "${services[@]}" | tee /tmp/services.txt echo "services=$(paste -sd, /tmp/services.txt)" >> "$GITHUB_OUTPUT" - name: Log in to registry if: steps.services.outputs.services != '' shell: bash run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "${REGISTRY}" \ -u "${{ secrets.REGISTRY_USERNAME }}" \ --password-stdin - name: Build and push changed images if: steps.services.outputs.services != '' shell: bash run: | IFS=, read -r -a services <<< "${{ steps.services.outputs.services }}" for service in "${services[@]}"; do case "$service" in dtek_notif) image="${REGISTRY}/forust/dtek-notif" tags=("latest") case "${GITHUB_REF_NAME}" in main) tags+=("main" "prod") ;; dev) tags+=("dev") ;; esac build_args=() for tag in "${tags[@]}"; do build_args+=(-t "${image}:${tag}") done docker build "${build_args[@]}" dtek_notif for tag in "${tags[@]}"; do docker push "${image}:${tag}" done ;; errorpages) image="${REGISTRY}/forust/error-pages" tags=("latest") case "${GITHUB_REF_NAME}" in main) tags+=("main" "prod") ;; dev) tags+=("dev") ;; esac build_args=() for tag in "${tags[@]}"; do build_args+=(-t "${image}:${tag}") done docker build "${build_args[@]}" errorpages for tag in "${tags[@]}"; do docker push "${image}:${tag}" done ;; userbot) image="${REGISTRY}/forust/userbot" tags=("latest") case "${GITHUB_REF_NAME}" in main) tags+=("main" "prod") ;; dev) tags+=("dev") ;; esac build_args=() for tag in "${tags[@]}"; do build_args+=(-t "${image}:${tag}") done docker build "${build_args[@]}" userbot for tag in "${tags[@]}"; do docker push "${image}:${tag}" done ;; homepages) for service in forust xdfnx; do case "$service" in forust) image="${REGISTRY}/forust/forust-homepage" ;; xdfnx) image="${REGISTRY}/forust/xdfnx-homepage" ;; esac tags=("latest") case "${GITHUB_REF_NAME}" in main) tags+=("main" "prod") ;; dev) tags+=("dev") ;; esac build_args=() for tag in "${tags[@]}"; do build_args+=(-t "${image}:${tag}") done docker build "${build_args[@]}" -f "homepages/Dockerfile.${service}" homepages for tag in "${tags[@]}"; do docker push "${image}:${tag}" done done ;; edu_master) for service in session-keeper webinar-checker; do case "$service" in session-keeper) context="edu_master/phpsessid-bot" image="${REGISTRY}/forust/session-keeper" ;; webinar-checker) context="edu_master/webinar-checker" image="${REGISTRY}/forust/webinar-checker" ;; esac tags=("latest") case "${GITHUB_REF_NAME}" in main) tags+=("main" "prod") ;; dev) tags+=("dev") ;; esac build_args=() for tag in "${tags[@]}"; do build_args+=(-t "${image}:${tag}") done docker build "${build_args[@]}" "$context" for tag in "${tags[@]}"; do docker push "${image}:${tag}" done done ;; esac done