Skip to content

feat(ci): add hadolint, markdownlint, and kubescape gates - #4846

Draft
Tanguille wants to merge 2 commits into
mainfrom
feat/shell-lint-ci-v2
Draft

feat(ci): add hadolint, markdownlint, and kubescape gates#4846
Tanguille wants to merge 2 commits into
mainfrom
feat/shell-lint-ci-v2

Conversation

@Tanguille

@Tanguille Tanguille commented Sep 3, 2026

Copy link
Copy Markdown
Owner

What changed

Extends the Shell Lint gate with three more CI linters covering the rest of the
repo surface: Dockerfiles, markdown, and the Kubernetes GitOps tree.

  • Docker Lint (blocking) — hadolint over every tracked Dockerfile.
    Adds .hadolint.yaml documenting the four rules the talos-kernel build
    image intentionally deviates from:
    • DL3003 — the kernel Makefile must run with the source tree as CWD, so the
      cd form is kept (a WORKDIR would leak into the final stage).
    • DL3018 — the source stage is build-only (GPG-verifies the kernel
      tarball); no layer crosses into the image, so pinning apk versions is churn.
    • DL3067COPY --from=llvm / / is deliberate: the builder needs the
      complete LLVM toolchain the Sidero tools image provides.
    • DL4006xz -cd tar.xz | gpgv pipes because GPG verifies the uncompressed
      tar; a corrupt stream yields a short/empty input and gpgv fails, so the
      && chain still aborts.
  • Markdown Lint (blocking) — markdownlint-cli2 over all markdown.
    Disables the noisy MD060 (table-column-style) rule and autofixes the
    remaining 31 issues (27 via --fix, 4 manual fence-language tags).
  • K8s Scan (advisory) — kubescape over kubernetes/, uploaded as a
    kubescape-report artifact. Not a merge gate: the GitOps tree intentionally
    runs privileged infrastructure (GPU driver, Ceph, node operators), so the
    pre-existing High-severity findings are reviewed in the artifact rather than
    blocked. zizmor remains the blocking gate for workflow/manifest structure.

Dockerfile note

The talos-kernel SPDX block was a cat <<EOF heredoc followed by a validation
RUN and then FROM scratch. hadolint's parser cannot handle any instruction
following a heredoc in a multi-stage build, so the heredoc is rewritten as a single
python3 -c that emits the identical SPDX JSON (version interpolated from
$KERNEL_VERSION). Output is byte-for-byte equivalent; the following validation
RUN still asserts the file parses.

Tooling

Pins hadolint 2.15.1, markdownlint-cli2 0.23.2, and kubescape 4.0.13 in
.mise.toml and regenerates mise.lock. All three new workflows are
zizmor-clean.

Verification (local, before push)

  • hadolint (with .hadolint.yaml): 0 findings, exit 0
  • markdownlint-cli2 '**/*.md': 0 issues across 41 files
  • zizmor --offline on all three workflows: no findings
  • kubescape scan kubernetes: 145 resources passed / 25 failed (pre-existing
    High findings from intentional infrastructure; report only)
  • mise fmt --check: clean

Adds a Shell Lint workflow (shellcheck + shfmt via mise, excluding
archive/) that runs on push/PR touching any *.sh, .editorconfig, or
the workflow itself. Pin shfmt 3.13.1 (mvdan/sh) in .mise.toml and
mise.lock.

Formats the five non-archive shell scripts to shfmt style so the
whole-repo gate is green from day one:
- validate-pr.sh: &>/dev/null -> &>/dev/null, > /dev/null -> >/dev/null
- nextcloud cron.sh / post-start.sh: 2-space -> 4-space indentation
- talos-kernel-build.sh: split compound one-liners, redirect spacing
- talos-template-vars.sh: 4-space indentation, line wrapping
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tanguille-cluster

Copy link
Copy Markdown
@@ data.cron.sh @@
# v1/ConfigMap/default/nextcloud-scripts
! ± value change in multiline text (twelve inserts, twelve deletions)
  #!/bin/bash
  set -u
  
  # Change to Nextcloud directory (required for occ commands)
- cd /var/www/html || { echo "ERROR: Cannot cd to /var/www/html" >&2; exit 1; }
+ cd /var/www/html || {
+     echo "ERROR: Cannot cd to /var/www/html" >&2
+     exit 1
+ }
  
  # Helper function to run occ commands with error handling
  run_occ() {
-   local cmd="$1"
-   local warning_msg="${2:-${cmd} failed}"
-   php occ "$cmd" || echo "WARNING: $warning_msg" >&2
+     local cmd="$1"
+     local warning_msg="${2:-${cmd} failed}"
+     php occ "$cmd" || echo "WARNING: $warning_msg" >&2
  }
  
  # Helper function to check if an app is installed
  app_installed() {
-   echo "$INSTALLED_APPS" | grep -q "$1"
+     echo "$INSTALLED_APPS" | grep -q "$1"
  }
  
  # Helper function to run occ command if app is installed
  run_if_app_installed() {
-   local app="$1"
-   local cmd="$2"
-   local msg="${3:-Running $app operation...}"
-   if app_installed "$app"; then
-     echo "$msg"
-     run_occ "$cmd"
-   fi
+     local app="$1"
+     local cmd="$2"
+     local msg="${3:-Running $app operation...}"
+     if app_installed "$app"; then
+         echo "$msg"
+         run_occ "$cmd"
+     fi
  }
  
  echo "Starting Nextcloud cron job at $(date)"
  
  # Not run_occ: it swallows the exit code, so the guard could never fire
  if ! php occ status >/dev/null 2>&1; then
-   echo "WARNING: Nextcloud not ready, skipping cron job" >&2
-   exit 0
+     echo "WARNING: Nextcloud not ready, skipping cron job" >&2
+     exit 0
  fi
  
  # Cached once: each `occ` call is a full Nextcloud bootstrap
  INSTALLED_APPS="$(php occ app:list)"
  
  # Run standard Nextcloud cron (every 5 minutes)
  # This is the main cron job that Nextcloud requires
  if ! php -f /var/www/html/cron.php; then
-   echo "ERROR: Nextcloud cron.php failed" >&2
-   # Don't exit - allow maintenance operations to run even if cron.php fails
+     echo "ERROR: Nextcloud cron.php failed" >&2
+     # Don't exit - allow maintenance operations to run even if cron.php fails
  fi
  
  # Run maintenance operations based on time
  # Note: Container timezone may be UTC, adjust hour accordingly
  
  [two lines unchanged)]
  
  DAY_OF_WEEK=$(date +%u)
  
  # Run database maintenance and cleanup every hour (at minute 0)
  if [ "$MINUTE" = "00" ]; then
-   echo "Running hourly maintenance operations..."
-   run_occ "db:add-missing-indices"
-   run_occ "db:add-missing-primary-keys"
-   run_occ "db:add-missing-columns"
-   run_occ "files:cleanup"
+     echo "Running hourly maintenance operations..."
+     run_occ "db:add-missing-indices"
+     run_occ "db:add-missing-primary-keys"
+     run_occ "db:add-missing-columns"
+     run_occ "files:cleanup"
  fi
  
  # Run very expensive operations less frequently (once per day at 2 AM UTC)
  # files:scan --all is VERY expensive and can take a long time
  # Adjust the hour if needed (0-23) - note this is UTC time
  if [ "$MINUTE" = "00" ] && [ "$HOUR" = "02" ]; then
-   echo "Running daily expensive maintenance operations..."
-   run_occ "maintenance:repair --include-expensive"
-   run_occ "files:scan --all"
-   run_if_app_installed "memories" "memories:index" "Running Memories indexing..."
+     echo "Running daily expensive maintenance operations..."
+     run_occ "maintenance:repair --include-expensive"
+     run_occ "files:scan --all"
+     run_if_app_installed "memories" "memories:index" "Running Memories indexing..."
  fi
  
  # Run Memories places setup weekly (Sunday at 3 AM UTC)
  if [ "$MINUTE" = "00" ] && [ "$HOUR" = "03" ] && [ "$DAY_OF_WEEK" = "7" ]; then
-   run_if_app_installed "memories" "memories:places-setup" "Running Memories places setup..."
+     run_if_app_installed "memories" "memories:places-setup" "Running Memories places setup..."
  fi
  
  # Run Recognize face clustering weekly (Sunday at 4 AM UTC)
  # This is expensive and should run after Memories places setup
  if [ "$MINUTE" = "00" ] && [ "$HOUR" = "04" ] && [ "$DAY_OF_WEEK" = "7" ]; then
-   run_if_app_installed "recognize" "recognize:cluster-faces" "Running Recognize face clustering..."
+     run_if_app_installed "recognize" "recognize:cluster-faces" "Running Recognize face clustering..."
  fi
  
  # Run Recognize background job (every 5 minutes to process queued files)
  # This processes face recognition, object detection, landmark recognition, and audio tagging queues
  
  [six lines unchanged)]
  
  # Manual sorting/naming is preserved - the app won't overwrite manually configured faces
  # The job will stop after 15 minutes (timeout) and continue in the next run
  # This distributes the load and prevents the job from running indefinitely
  if [ "$((MINUTE % 15))" = "0" ]; then
-   if app_installed "facerecognition"; then
-     echo "Running Face Recognition background job (will stop after 15 minutes)..."
-     # The app has internal locking (LockTask) to prevent concurrent execution
-     # If a previous job is still running, this will fail gracefully due to the lock
-     run_occ "face:background_job" "face:background_job failed (may be locked by another instance)"
-   fi
+     if app_installed "facerecognition"; then
+         echo "Running Face Recognition background job (will stop after 15 minutes)..."
+         # The app has internal locking (LockTask) to prevent concurrent execution
+         # If a previous job is still running, this will fail gracefully due to the lock
+         run_occ "face:background_job" "face:background_job failed (may be locked by another instance)"
+     fi
  fi
  
  # Run Face Recognition album sync (every hour at minute 15)
  # This syncs photo albums in the Photos app with recognized faces
  # Albums are editable in Photos app, but changes are reverted on next sync
  if [ "$MINUTE" = "15" ]; then
-   run_if_app_installed "facerecognition" "face:sync-albums" "Running Face Recognition album sync..."
+     run_if_app_installed "facerecognition" "face:sync-albums" "Running Face Recognition album sync..."
  fi
  
  echo "Nextcloud cron job completed at $(date)"

@@ data.post-start.sh @@
# v1/ConfigMap/default/nextcloud-scripts
! ± value change in multiline text (nine inserts, ten deletions)
  #!/bin/bash
  set -u
  
  LOG_FILE="/var/log/post-start.log"
  mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
  
  log() {
-   local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
-   echo "$*"
-   echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
+     local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
+     echo "$*"
+     echo "$msg" >>"$LOG_FILE" 2>/dev/null || true
  }
  
  run_occ() {
-   local cmd="cd /var/www/html && php occ"
-   for arg in "$@"; do
-     cmd="$cmd $(printf '%q' "$arg")"
-   done
-   su -s /bin/sh www-data -c "$cmd" 2>/dev/null
+     local cmd="cd /var/www/html && php occ"
+     for arg in "$@"; do
+         cmd="$cmd $(printf '%q' "$arg")"
+     done
+     su -s /bin/sh www-data -c "$cmd" 2>/dev/null
  }
  
  set_config() {
-   local type="$1"
-   shift
-   # Best effort: a failed config set must not stop the rest. The readiness check below needs
-   # run_occ's real exit code, so the tolerance lives here rather than in run_occ.
-   run_occ "config:$type:set" "$@" || true
+     local type="$1"
+     shift
+     # Best effort: a failed config set must not stop the rest. The readiness check below needs
+     # run_occ's real exit code, so the tolerance lives here rather than in run_occ.
+     run_occ "config:$type:set" "$@" || true
  }
  
  find_tool() {
-   local tool="$1"
-   local path
-   path=$(command -v "$tool" 2>/dev/null)
-   [ -n "$path" ] && echo "$path" && return
-   find /usr/bin /usr/local/bin -maxdepth 2 -name "$tool" -type f -executable 2>/dev/null | head -1
+     local tool="$1"
+     local path
+     path=$(command -v "$tool" 2>/dev/null)
+     [ -n "$path" ] && echo "$path" && return
+     find /usr/bin /usr/local/bin -maxdepth 2 -name "$tool" -type f -executable 2>/dev/null | head -1
  }
  
  log "=== Post-start script started ==="
  
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq || log "WARNING: apt-get update failed"
  
  for pkg in libimage-exiftool-perl ffmpeg imagemagick libmagickcore-7.q16-10 libmagickwand-7.q16-10 nodejs npm; do
-   if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
-     log "Installing $pkg..."
-     apt-get install -y --no-install-recommends "$pkg" >/dev/null 2>&1 || log "WARNING: Failed to install $pkg"
-   fi
+     if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "install ok installed"; then
+         log "Installing $pkg..."
+         apt-get install -y --no-install-recommends "$pkg" >/dev/null 2>&1 || log "WARNING: Failed to install $pkg"
+     fi
  done
  
  log "Waiting for Nextcloud to be ready..."
  ready=0
  for i in {1..30}; do
-   if run_occ "status" >/dev/null 2>&1; then
-     log "Nextcloud is ready"
-     ready=1
-     break
-   fi
-   [ "$i" -lt 30 ] && sleep 2
+     if run_occ "status" >/dev/null 2>&1; then
+         log "Nextcloud is ready"
+         ready=1
+         break
+     fi
+     [ "$i" -lt 30 ] && sleep 2
  done
  [ "$ready" -eq 0 ] && log "WARNING: Nextcloud not ready after 60 seconds"
- 
- 
  
  log "Configuring tools..."
  tool_path=$(find_tool "convert")
  [ -n "$tool_path" ] && set_config "system" "preview_imagick_path" --value="$tool_path"
  
  tool_path=$(find_tool "exiftool")
  if [ -n "$tool_path" ]; then
-   log "Found exiftool at $tool_path"
-   set_config "app" "memories exiftool" --value="$tool_path"
+     log "Found exiftool at $tool_path"
+     set_config "app" "memories exiftool" --value="$tool_path"
  else
-   log "WARNING: exiftool not found"
+     log "WARNING: exiftool not found"
  fi
  
  if [ "$ready" -eq 1 ]; then
-   set_config "app" "memories enable_transitions" --value="yes"
-   set_config "app" "memories preview_max_x" --value="2048"
-   set_config "app" "memories preview_max_y" --value="2048"
+     set_config "app" "memories enable_transitions" --value="yes"
+     set_config "app" "memories preview_max_x" --value="2048"
+     set_config "app" "memories preview_max_y" --value="2048"
  fi
  
  log "=== Post-start script completed ==="

@deepsource-io

deepsource-io Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

DeepSource Code Review

We reviewed changes in b35196f...60b25b3 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Sep 5, 2026 2:52a.m. Review ↗
Shell Sep 5, 2026 2:52a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

Add three CI lint workflows on top of the existing Shell Lint gate:

- docker-lint.yaml: hadolint over tracked Dockerfiles (blocking).
  Add .hadolint.yaml documenting the four rules the talos-kernel
  build image intentionally deviates from (DL3003 cd-based kernel
  build, DL3018 build-only source stage, DL3067 whole-toolchain COPY,
  DL4006 pipe into gpgv for GPG verification of the uncompressed tar).
  The talos-kernel SPDX heredoc is rewritten as a single python3 -c so
  the file parses under hadolint's parser, which cannot handle an
  instruction following a heredoc in a multi-stage build.
- markdown-lint.yaml: markdownlint-cli2 over all markdown (blocking).
  Disable the noisy MD060 table-column-style rule and autofix the
  remaining 31 issues (27 via --fix, 4 manual fence-language tags).
- k8s-scan.yaml: kubescape over kubernetes/ as an advisory report
  (uploaded as an artifact, not a merge gate). The GitOps tree
  intentionally runs privileged infrastructure (GPU driver, Ceph,
  node operators), so the 31 pre-existing High-severity findings are
  reviewed rather than blocked. zizmor remains the blocking gate for
  workflow/manifest structure.

Pin hadolint 2.15.1, markdownlint-cli2 0.23.2, and kubescape 4.0.13 in
.mise.toml and regenerate mise.lock.
@Tanguille Tanguille changed the title feat(ci): add shell linting gate and format all shell scripts feat(ci): add hadolint, markdownlint, and kubescape gates Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant