From f6991d283d14c81da0eb2048eab4822436c22638 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Mon, 27 Oct 2025 12:05:58 -0400 Subject: [PATCH 01/11] chore: Collect talos node support bundles Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/scripts/collect-talos-diagnostics.sh | 151 +++++++++++++++++++ .github/workflows/ci.yaml | 17 +++ 2 files changed, 168 insertions(+) create mode 100755 .github/scripts/collect-talos-diagnostics.sh diff --git a/.github/scripts/collect-talos-diagnostics.sh b/.github/scripts/collect-talos-diagnostics.sh new file mode 100755 index 00000000..77a8339c --- /dev/null +++ b/.github/scripts/collect-talos-diagnostics.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +# Script to collect talosctl diagnostics for support bundle +# This script will be run as part of the support bundle collection process + +set -euo pipefail + +# Create output directory +TALOS_DIAGNOSTICS_DIR="${1:-/tmp/talos-diagnostics}" +mkdir -p "$TALOS_DIAGNOSTICS_DIR" + +# Function to safely run talosctl commands +run_talosctl() { + local cmd="$1" + local output_file="$2" + local description="$3" + + echo "Collecting: $description" + if eval "$cmd" > "$output_file" 2>&1; then + echo "✓ Successfully collected: $description" + else + echo "⚠ Failed to collect: $description (exit code: $?)" + echo "Command failed: $cmd" > "$output_file" + fi +} + +# Check if talosctl is available +if ! command -v talosctl &> /dev/null; then + echo "talosctl not found, skipping talos diagnostics" + exit 0 +fi + +# Get cluster nodes (if available) +NODES="" +if kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | grep -q .; then + NODES=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | tr ' ' ',') +fi + +# If no nodes found via kubectl, try to get from talosctl config +if [ -z "$NODES" ]; then + if talosctl config info 2>/dev/null | grep -q "endpoints:"; then + NODES=$(talosctl config info 2>/dev/null | grep "endpoints:" | cut -d: -f2 | tr -d ' ' | tr ',' ' ') + fi +fi + +# Set node flag if we have nodes +NODE_FLAG="" +if [ -n "$NODES" ]; then + NODE_FLAG="--nodes $NODES" +fi + +echo "Collecting Talos diagnostics to: $TALOS_DIAGNOSTICS_DIR" +echo "Target nodes: ${NODES:-'auto-detect'}" + +# 1. Comprehensive support bundle (most important) +run_talosctl "talosctl support $NODE_FLAG --output $TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" \ + "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" \ + "Complete Talos support bundle" + +# 2. Cluster health check +run_talosctl "talosctl health $NODE_FLAG" \ + "$TALOS_DIAGNOSTICS_DIR/talos-health.txt" \ + "Cluster health status" + +# 3. Version information +run_talosctl "talosctl version $NODE_FLAG" \ + "$TALOS_DIAGNOSTICS_DIR/talos-version.txt" \ + "Talos version information" + +# 4. Node information (if we have specific nodes) +if [ -n "$NODES" ]; then + for node in $(echo "$NODES" | tr ',' ' '); do + node_dir="$TALOS_DIAGNOSTICS_DIR/node-$node" + mkdir -p "$node_dir" + + # Kernel logs + run_talosctl "talosctl dmesg --nodes $node" \ + "$node_dir/dmesg.txt" \ + "Kernel logs for node $node" + + # Running processes + run_talosctl "talosctl processes --nodes $node" \ + "$node_dir/processes.txt" \ + "Running processes for node $node" + + # Memory usage + run_talosctl "talosctl memory --nodes $node" \ + "$node_dir/memory.txt" \ + "Memory usage for node $node" + + # Network connections + run_talosctl "talosctl netstat --nodes $node" \ + "$node_dir/netstat.txt" \ + "Network connections for node $node" + + # Disk usage + run_talosctl "talosctl usage --nodes $node" \ + "$node_dir/disk-usage.txt" \ + "Disk usage for node $node" + + # Running containers + run_talosctl "talosctl containers --nodes $node" \ + "$node_dir/containers.txt" \ + "Running containers for node $node" + + # Service status + run_talosctl "talosctl service --nodes $node" \ + "$node_dir/services.txt" \ + "Service status for node $node" + + # Mount information + run_talosctl "talosctl mounts --nodes $node" \ + "$node_dir/mounts.txt" \ + "Mount information for node $node" + done +fi + +# 5. etcd status (control plane nodes) +run_talosctl "talosctl etcd status $NODE_FLAG" \ + "$TALOS_DIAGNOSTICS_DIR/etcd-status.txt" \ + "etcd cluster status" + +# 6. Cluster configuration +run_talosctl "talosctl get config $NODE_FLAG -o yaml" \ + "$TALOS_DIAGNOSTICS_DIR/talos-config.yaml" \ + "Talos cluster configuration" + +# 7. Available resource definitions +run_talosctl "talosctl get rd $NODE_FLAG" \ + "$TALOS_DIAGNOSTICS_DIR/resource-definitions.txt" \ + "Available resource definitions" + +# 8. COSI resources (if accessible) +run_talosctl "talosctl get machines $NODE_FLAG -o yaml" \ + "$TALOS_DIAGNOSTICS_DIR/machines.yaml" \ + "Machine resources" + +# 9. Events (if accessible) +run_talosctl "talosctl events $NODE_FLAG --duration 1h" \ + "$TALOS_DIAGNOSTICS_DIR/talos-events.txt" \ + "Talos events (last hour)" + +echo "Talos diagnostics collection completed" +echo "Output directory: $TALOS_DIAGNOSTICS_DIR" +ls -la "$TALOS_DIAGNOSTICS_DIR" + +# Create tar.gz archive in the parent directory (same pattern as Windsor state) +TAR_FILE="$(dirname "$TALOS_DIAGNOSTICS_DIR")/talos-diagnostics.tar.gz" +echo "Creating archive: $TAR_FILE" +tar -czf "$TAR_FILE" -C "$(dirname "$TALOS_DIAGNOSTICS_DIR")" "$(basename "$TALOS_DIAGNOSTICS_DIR")" +echo "✓ Talos diagnostics archived to: $TAR_FILE" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 109594f3..647171a7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -165,6 +165,23 @@ jobs: run: | tar --exclude='.docker-cache' --exclude='.terraform' -czf support-bundles/windsor-state.tar.gz contexts/local .windsor + - name: Collect Talos Diagnostics + if: always() + run: | + # Install talosctl if not available + if ! command -v talosctl &> /dev/null; then + echo "Installing talosctl..." + cd "$(mktemp -d)" && + curl -fsSLO "https://github.com/siderolabs/talos/releases/latest/download/talosctl-linux-amd64" && + chmod +x talosctl-linux-amd64 && + sudo mv talosctl-linux-amd64 /usr/local/bin/talosctl && + cd - && + rm -rf "$OLDPWD" + fi + + # Run talos diagnostics collection + .github/scripts/collect-talos-diagnostics.sh support-bundles/talos-diagnostics + - name: Collect support bundle if: always() run: | From 50716face6b599cac44a61350dc2fc5dd512cf91 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Mon, 27 Oct 2025 13:41:13 -0400 Subject: [PATCH 02/11] Test Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/scripts/collect-talos-diagnostics.sh | 34 ++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/scripts/collect-talos-diagnostics.sh b/.github/scripts/collect-talos-diagnostics.sh index 77a8339c..c61cb9ad 100755 --- a/.github/scripts/collect-talos-diagnostics.sh +++ b/.github/scripts/collect-talos-diagnostics.sh @@ -16,11 +16,16 @@ run_talosctl() { local description="$3" echo "Collecting: $description" + echo "Command: $cmd" if eval "$cmd" > "$output_file" 2>&1; then echo "✓ Successfully collected: $description" else - echo "⚠ Failed to collect: $description (exit code: $?)" + local exit_code=$? + echo "⚠ Failed to collect: $description (exit code: $exit_code)" echo "Command failed: $cmd" > "$output_file" + echo "Exit code: $exit_code" >> "$output_file" + echo "Error output:" >> "$output_file" + eval "$cmd" >> "$output_file" 2>&1 || true fi } @@ -52,9 +57,16 @@ fi echo "Collecting Talos diagnostics to: $TALOS_DIAGNOSTICS_DIR" echo "Target nodes: ${NODES:-'auto-detect'}" +# Debug: Show talosctl version and available commands +echo "Talosctl version:" +talosctl version --client 2>/dev/null || echo "Failed to get version" +echo "Available talosctl commands:" +talosctl --help 2>/dev/null | grep -A 100 "Available Commands:" | head -20 || echo "Failed to get help" + # 1. Comprehensive support bundle (most important) -run_talosctl "talosctl support $NODE_FLAG --output $TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" \ - "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" \ +# Note: talosctl support requires specific node targeting, so we'll try without --output first +run_talosctl "talosctl support $NODE_FLAG" \ + "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.txt" \ "Complete Talos support bundle" # 2. Cluster health check @@ -93,8 +105,8 @@ if [ -n "$NODES" ]; then "$node_dir/netstat.txt" \ "Network connections for node $node" - # Disk usage - run_talosctl "talosctl usage --nodes $node" \ + # Disk usage (using df instead of usage which might not be available) + run_talosctl "talosctl df --nodes $node" \ "$node_dir/disk-usage.txt" \ "Disk usage for node $node" @@ -120,11 +132,16 @@ run_talosctl "talosctl etcd status $NODE_FLAG" \ "$TALOS_DIAGNOSTICS_DIR/etcd-status.txt" \ "etcd cluster status" -# 6. Cluster configuration +# 6. Cluster configuration (try different approaches) run_talosctl "talosctl get config $NODE_FLAG -o yaml" \ "$TALOS_DIAGNOSTICS_DIR/talos-config.yaml" \ "Talos cluster configuration" +# Alternative: try to get machine config +run_talosctl "talosctl get machineconfig $NODE_FLAG -o yaml" \ + "$TALOS_DIAGNOSTICS_DIR/talos-machine-config.yaml" \ + "Talos machine configuration" + # 7. Available resource definitions run_talosctl "talosctl get rd $NODE_FLAG" \ "$TALOS_DIAGNOSTICS_DIR/resource-definitions.txt" \ @@ -135,6 +152,11 @@ run_talosctl "talosctl get machines $NODE_FLAG -o yaml" \ "$TALOS_DIAGNOSTICS_DIR/machines.yaml" \ "Machine resources" +# Alternative: try to get nodes +run_talosctl "talosctl get nodes $NODE_FLAG -o yaml" \ + "$TALOS_DIAGNOSTICS_DIR/nodes.yaml" \ + "Node resources" + # 9. Events (if accessible) run_talosctl "talosctl events $NODE_FLAG --duration 1h" \ "$TALOS_DIAGNOSTICS_DIR/talos-events.txt" \ From 8310f1b80828f6733c8672bc7c63ec935899138f Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:37:14 -0400 Subject: [PATCH 03/11] Grab talos support diagnostics Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/scripts/collect-talos-diagnostics.sh | 228 ++++++++++--------- .github/workflows/ci.yaml | 4 +- 2 files changed, 123 insertions(+), 109 deletions(-) diff --git a/.github/scripts/collect-talos-diagnostics.sh b/.github/scripts/collect-talos-diagnostics.sh index c61cb9ad..5f41fcc2 100755 --- a/.github/scripts/collect-talos-diagnostics.sh +++ b/.github/scripts/collect-talos-diagnostics.sh @@ -5,6 +5,9 @@ set -euo pipefail +# Handle script interruption gracefully +trap 'echo "Script interrupted, cleaning up..."; exit 130' INT TERM + # Create output directory TALOS_DIAGNOSTICS_DIR="${1:-/tmp/talos-diagnostics}" mkdir -p "$TALOS_DIAGNOSTICS_DIR" @@ -29,29 +32,80 @@ run_talosctl() { fi } +# Function to run commands with timeout (fallback if timeout command not available) +run_with_timeout() { + local timeout_seconds="$1" + local cmd="$2" + local output_file="$3" + local description="$4" + + echo "Collecting: $description" + echo "Command: $cmd" + + # Try to use timeout command if available + if command -v timeout >/dev/null 2>&1; then + if timeout "$timeout_seconds" bash -c "$cmd" > "$output_file" 2>&1; then + echo "✓ Successfully collected: $description" + else + local exit_code=$? + echo "⚠ Failed to collect: $description (exit code: $exit_code)" + echo "Command failed: $cmd" > "$output_file" + echo "Exit code: $exit_code" >> "$output_file" + fi + else + # Fallback: run without timeout but with background process and kill + echo "Warning: timeout command not available, using background process with kill" + ( + eval "$cmd" > "$output_file" 2>&1 & + local pid=$! + sleep "$timeout_seconds" + if kill -0 "$pid" 2>/dev/null; then + echo "Command timed out, killing process" + kill -TERM "$pid" 2>/dev/null + sleep 2 + kill -KILL "$pid" 2>/dev/null + echo "Command timed out after ${timeout_seconds}s" >> "$output_file" + echo "⚠ Failed to collect: $description (timeout)" + else + wait "$pid" + local wait_exit_code=$? + if [ $wait_exit_code -eq 0 ]; then + echo "✓ Successfully collected: $description" + else + echo "⚠ Failed to collect: $description (exit code: $wait_exit_code)" + fi + fi + ) + fi +} + # Check if talosctl is available if ! command -v talosctl &> /dev/null; then echo "talosctl not found, skipping talos diagnostics" exit 0 fi -# Get cluster nodes (if available) +# Determine node targeting strategy based on environment +NODE_FLAG="" NODES="" -if kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | grep -q .; then - NODES=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | tr ' ' ',') -fi - -# If no nodes found via kubectl, try to get from talosctl config -if [ -z "$NODES" ]; then - if talosctl config info 2>/dev/null | grep -q "endpoints:"; then - NODES=$(talosctl config info 2>/dev/null | grep "endpoints:" | cut -d: -f2 | tr -d ' ' | tr ',' ' ') - fi -fi -# Set node flag if we have nodes -NODE_FLAG="" -if [ -n "$NODES" ]; then - NODE_FLAG="--nodes $NODES" +# Check if we're in a local Docker Desktop environment (has controlplane-1 node) +if talosctl get nodes 2>/dev/null | grep -q "controlplane-1"; then + echo "Detected local Docker Desktop environment, using node names" + NODE_FLAG="-n controlplane-1" + NODES="controlplane-1" +# Check if we have specific node IPs from kubectl (CI environment) +elif kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | grep -q .; then + NODES=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | tr ' ' ',') + NODE_FLAG="-n $NODES" + echo "Detected CI environment, using node IPs: $NODES" +# Fallback: try to get from talosctl config +elif talosctl config info 2>/dev/null | grep -q "endpoints:"; then + NODES=$(talosctl config info 2>/dev/null | grep "endpoints:" | cut -d: -f2 | tr -d ' ' | tr ',' ' ') + NODE_FLAG="-n $NODES" + echo "Using endpoints from talosctl config: $NODES" +else + echo "No nodes detected, will attempt commands without node targeting" fi echo "Collecting Talos diagnostics to: $TALOS_DIAGNOSTICS_DIR" @@ -63,105 +117,58 @@ talosctl version --client 2>/dev/null || echo "Failed to get version" echo "Available talosctl commands:" talosctl --help 2>/dev/null | grep -A 100 "Available Commands:" | head -20 || echo "Failed to get help" +# Test connectivity to nodes before proceeding +if [ -n "$NODES" ]; then + echo "Testing connectivity to nodes..." + for node in $(echo "$NODES" | tr ',' ' '); do + echo "Testing node: $node" + if timeout 10s talosctl version --nodes "$node" >/dev/null 2>&1; then + echo "✓ Node $node is reachable" + else + echo "⚠ Node $node is not reachable or not responding" + fi + done +fi + # 1. Comprehensive support bundle (most important) -# Note: talosctl support requires specific node targeting, so we'll try without --output first -run_talosctl "talosctl support $NODE_FLAG" \ - "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.txt" \ - "Complete Talos support bundle" +# This creates a complete tar.gz file with all diagnostics +echo "Collecting comprehensive Talos support bundle..." + +# Change to the diagnostics directory to avoid conflicts with existing support.zip +cd "$TALOS_DIAGNOSTICS_DIR" + +if [ -n "$NODE_FLAG" ]; then + echo "Using node targeting: $NODE_FLAG" + echo "Running: talosctl support $NODE_FLAG --output talos-support-bundle.tar.gz" + if talosctl support $NODE_FLAG --output talos-support-bundle.tar.gz; then + echo "✓ Successfully created talos-support-bundle.tar.gz" + else + exit_code=$? + echo "⚠ Support bundle creation failed (exit code: $exit_code)" + echo "Command failed: talosctl support $NODE_FLAG --output talos-support-bundle.tar.gz" > support-bundle-output.txt + echo "Exit code: $exit_code" >> support-bundle-output.txt + fi +else + echo "No node targeting available, attempting support bundle without targeting" + echo "Running: talosctl support --output talos-support-bundle.tar.gz" + if talosctl support --output talos-support-bundle.tar.gz; then + echo "✓ Successfully created talos-support-bundle.tar.gz" + else + exit_code=$? + echo "⚠ Support bundle creation failed (exit code: $exit_code)" + echo "Command failed: talosctl support --output talos-support-bundle.tar.gz" > support-bundle-output.txt + echo "Exit code: $exit_code" >> support-bundle-output.txt + fi +fi -# 2. Cluster health check -run_talosctl "talosctl health $NODE_FLAG" \ - "$TALOS_DIAGNOSTICS_DIR/talos-health.txt" \ - "Cluster health status" +# Change back to original directory +cd - > /dev/null -# 3. Version information +# 2. Basic version info (always works) run_talosctl "talosctl version $NODE_FLAG" \ "$TALOS_DIAGNOSTICS_DIR/talos-version.txt" \ "Talos version information" -# 4. Node information (if we have specific nodes) -if [ -n "$NODES" ]; then - for node in $(echo "$NODES" | tr ',' ' '); do - node_dir="$TALOS_DIAGNOSTICS_DIR/node-$node" - mkdir -p "$node_dir" - - # Kernel logs - run_talosctl "talosctl dmesg --nodes $node" \ - "$node_dir/dmesg.txt" \ - "Kernel logs for node $node" - - # Running processes - run_talosctl "talosctl processes --nodes $node" \ - "$node_dir/processes.txt" \ - "Running processes for node $node" - - # Memory usage - run_talosctl "talosctl memory --nodes $node" \ - "$node_dir/memory.txt" \ - "Memory usage for node $node" - - # Network connections - run_talosctl "talosctl netstat --nodes $node" \ - "$node_dir/netstat.txt" \ - "Network connections for node $node" - - # Disk usage (using df instead of usage which might not be available) - run_talosctl "talosctl df --nodes $node" \ - "$node_dir/disk-usage.txt" \ - "Disk usage for node $node" - - # Running containers - run_talosctl "talosctl containers --nodes $node" \ - "$node_dir/containers.txt" \ - "Running containers for node $node" - - # Service status - run_talosctl "talosctl service --nodes $node" \ - "$node_dir/services.txt" \ - "Service status for node $node" - - # Mount information - run_talosctl "talosctl mounts --nodes $node" \ - "$node_dir/mounts.txt" \ - "Mount information for node $node" - done -fi - -# 5. etcd status (control plane nodes) -run_talosctl "talosctl etcd status $NODE_FLAG" \ - "$TALOS_DIAGNOSTICS_DIR/etcd-status.txt" \ - "etcd cluster status" - -# 6. Cluster configuration (try different approaches) -run_talosctl "talosctl get config $NODE_FLAG -o yaml" \ - "$TALOS_DIAGNOSTICS_DIR/talos-config.yaml" \ - "Talos cluster configuration" - -# Alternative: try to get machine config -run_talosctl "talosctl get machineconfig $NODE_FLAG -o yaml" \ - "$TALOS_DIAGNOSTICS_DIR/talos-machine-config.yaml" \ - "Talos machine configuration" - -# 7. Available resource definitions -run_talosctl "talosctl get rd $NODE_FLAG" \ - "$TALOS_DIAGNOSTICS_DIR/resource-definitions.txt" \ - "Available resource definitions" - -# 8. COSI resources (if accessible) -run_talosctl "talosctl get machines $NODE_FLAG -o yaml" \ - "$TALOS_DIAGNOSTICS_DIR/machines.yaml" \ - "Machine resources" - -# Alternative: try to get nodes -run_talosctl "talosctl get nodes $NODE_FLAG -o yaml" \ - "$TALOS_DIAGNOSTICS_DIR/nodes.yaml" \ - "Node resources" - -# 9. Events (if accessible) -run_talosctl "talosctl events $NODE_FLAG --duration 1h" \ - "$TALOS_DIAGNOSTICS_DIR/talos-events.txt" \ - "Talos events (last hour)" - echo "Talos diagnostics collection completed" echo "Output directory: $TALOS_DIAGNOSTICS_DIR" ls -la "$TALOS_DIAGNOSTICS_DIR" @@ -169,5 +176,12 @@ ls -la "$TALOS_DIAGNOSTICS_DIR" # Create tar.gz archive in the parent directory (same pattern as Windsor state) TAR_FILE="$(dirname "$TALOS_DIAGNOSTICS_DIR")/talos-diagnostics.tar.gz" echo "Creating archive: $TAR_FILE" + +# Include the talos support bundle if it was created +if [ -f "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" ]; then + echo "Including talos support bundle in archive" + cp "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" "$(dirname "$TALOS_DIAGNOSTICS_DIR")/" +fi + tar -czf "$TAR_FILE" -C "$(dirname "$TALOS_DIAGNOSTICS_DIR")" "$(basename "$TALOS_DIAGNOSTICS_DIR")" echo "✓ Talos diagnostics archived to: $TAR_FILE" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 647171a7..f13d84ae 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -179,8 +179,8 @@ jobs: rm -rf "$OLDPWD" fi - # Run talos diagnostics collection - .github/scripts/collect-talos-diagnostics.sh support-bundles/talos-diagnostics + # Run talos diagnostics collection within Windsor environment + windsor exec -- .github/scripts/collect-talos-diagnostics.sh support-bundles/talos-diagnostics - name: Collect support bundle if: always() From c3ccfd53dfce53113a4c81f58aeb1833484f1b69 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:17:16 -0400 Subject: [PATCH 04/11] Correct talos support bundle Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/scripts/collect-talos-diagnostics.sh | 30 ++++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/scripts/collect-talos-diagnostics.sh b/.github/scripts/collect-talos-diagnostics.sh index 5f41fcc2..c8c2ac69 100755 --- a/.github/scripts/collect-talos-diagnostics.sh +++ b/.github/scripts/collect-talos-diagnostics.sh @@ -86,23 +86,23 @@ if ! command -v talosctl &> /dev/null; then fi # Determine node targeting strategy based on environment -NODE_FLAG="" +NODE_ARGS=() NODES="" # Check if we're in a local Docker Desktop environment (has controlplane-1 node) if talosctl get nodes 2>/dev/null | grep -q "controlplane-1"; then echo "Detected local Docker Desktop environment, using node names" - NODE_FLAG="-n controlplane-1" + NODE_ARGS=(-n controlplane-1) NODES="controlplane-1" # Check if we have specific node IPs from kubectl (CI environment) elif kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | grep -q .; then NODES=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null | tr ' ' ',') - NODE_FLAG="-n $NODES" + NODE_ARGS=(-n "$NODES") echo "Detected CI environment, using node IPs: $NODES" # Fallback: try to get from talosctl config elif talosctl config info 2>/dev/null | grep -q "endpoints:"; then NODES=$(talosctl config info 2>/dev/null | grep "endpoints:" | cut -d: -f2 | tr -d ' ' | tr ',' ' ') - NODE_FLAG="-n $NODES" + NODE_ARGS=(-n "$NODES") echo "Using endpoints from talosctl config: $NODES" else echo "No nodes detected, will attempt commands without node targeting" @@ -137,15 +137,15 @@ echo "Collecting comprehensive Talos support bundle..." # Change to the diagnostics directory to avoid conflicts with existing support.zip cd "$TALOS_DIAGNOSTICS_DIR" -if [ -n "$NODE_FLAG" ]; then - echo "Using node targeting: $NODE_FLAG" - echo "Running: talosctl support $NODE_FLAG --output talos-support-bundle.tar.gz" - if talosctl support $NODE_FLAG --output talos-support-bundle.tar.gz; then +if [ ${#NODE_ARGS[@]} -gt 0 ]; then + echo "Using node targeting: ${NODE_ARGS[*]}" + echo "Running: talosctl support ${NODE_ARGS[*]} --output talos-support-bundle.tar.gz" + if talosctl support "${NODE_ARGS[@]}" --output talos-support-bundle.tar.gz; then echo "✓ Successfully created talos-support-bundle.tar.gz" else exit_code=$? echo "⚠ Support bundle creation failed (exit code: $exit_code)" - echo "Command failed: talosctl support $NODE_FLAG --output talos-support-bundle.tar.gz" > support-bundle-output.txt + echo "Command failed: talosctl support ${NODE_ARGS[*]} --output talos-support-bundle.tar.gz" > support-bundle-output.txt echo "Exit code: $exit_code" >> support-bundle-output.txt fi else @@ -165,9 +165,15 @@ fi cd - > /dev/null # 2. Basic version info (always works) -run_talosctl "talosctl version $NODE_FLAG" \ - "$TALOS_DIAGNOSTICS_DIR/talos-version.txt" \ - "Talos version information" +if [ ${#NODE_ARGS[@]} -gt 0 ]; then + run_talosctl "talosctl version ${NODE_ARGS[*]}" \ + "$TALOS_DIAGNOSTICS_DIR/talos-version.txt" \ + "Talos version information" +else + run_talosctl "talosctl version" \ + "$TALOS_DIAGNOSTICS_DIR/talos-version.txt" \ + "Talos version information" +fi echo "Talos diagnostics collection completed" echo "Output directory: $TALOS_DIAGNOSTICS_DIR" From d2882d3fed3f8e5bfc7f0f8ed1a84798432baa30 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:04:30 -0400 Subject: [PATCH 05/11] Try to add support.zip bundle Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/scripts/collect-talos-diagnostics.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/scripts/collect-talos-diagnostics.sh b/.github/scripts/collect-talos-diagnostics.sh index c8c2ac69..7c89bf6c 100755 --- a/.github/scripts/collect-talos-diagnostics.sh +++ b/.github/scripts/collect-talos-diagnostics.sh @@ -187,7 +187,11 @@ echo "Creating archive: $TAR_FILE" if [ -f "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" ]; then echo "Including talos support bundle in archive" cp "$TALOS_DIAGNOSTICS_DIR/talos-support-bundle.tar.gz" "$(dirname "$TALOS_DIAGNOSTICS_DIR")/" + # Create archive with both the diagnostics directory and the support bundle + tar -czf "$TAR_FILE" -C "$(dirname "$TALOS_DIAGNOSTICS_DIR")" "$(basename "$TALOS_DIAGNOSTICS_DIR")" "talos-support-bundle.tar.gz" +else + # Create archive with just the diagnostics directory + tar -czf "$TAR_FILE" -C "$(dirname "$TALOS_DIAGNOSTICS_DIR")" "$(basename "$TALOS_DIAGNOSTICS_DIR")" fi -tar -czf "$TAR_FILE" -C "$(dirname "$TALOS_DIAGNOSTICS_DIR")" "$(basename "$TALOS_DIAGNOSTICS_DIR")" echo "✓ Talos diagnostics archived to: $TAR_FILE" From 0d9deed5309040d95e389fafc300f1b3aaf0a6b1 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:20:20 -0400 Subject: [PATCH 06/11] Collect talos docker logs Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/scripts/collect-talos-diagnostics.sh | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/scripts/collect-talos-diagnostics.sh b/.github/scripts/collect-talos-diagnostics.sh index 7c89bf6c..6606a94b 100755 --- a/.github/scripts/collect-talos-diagnostics.sh +++ b/.github/scripts/collect-talos-diagnostics.sh @@ -175,6 +175,30 @@ else "Talos version information" fi +# 3. Collect Docker logs from Talos nodes +echo "Collecting Docker logs from Talos nodes..." +docker_logs_dir="$TALOS_DIAGNOSTICS_DIR/docker-logs" +mkdir -p "$docker_logs_dir" + +# Get list of Talos containers +talos_containers=$(docker ps --format "table {{.Names}}\t{{.Image}}" | grep -E "(talos|controlplane|worker)" | awk '{print $1}') + +if [ -n "$talos_containers" ]; then + echo "Found Talos containers: $talos_containers" + for container in $talos_containers; do + echo "Collecting logs for container: $container" + if docker logs "$container" > "$docker_logs_dir/${container}.log" 2>&1; then + echo "✓ Successfully collected logs for $container" + else + echo "⚠ Failed to collect logs for $container" + echo "Failed to collect logs for container: $container" > "$docker_logs_dir/${container}.log" + fi + done +else + echo "No Talos containers found" + echo "No Talos containers found" > "$docker_logs_dir/no-containers.txt" +fi + echo "Talos diagnostics collection completed" echo "Output directory: $TALOS_DIAGNOSTICS_DIR" ls -la "$TALOS_DIAGNOSTICS_DIR" From 0e417d4bbaa61b492e355e759211743dc6377108 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:16:56 -0400 Subject: [PATCH 07/11] debug Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/workflows/ci.yaml | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f13d84ae..c7c61ba7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -157,8 +157,67 @@ jobs: - name: Windsor Up run: | + echo "=== DEBUG: Starting Windsor Up process ===" + echo "Current working directory: $(pwd)" + echo "WINDSOR_PROJECT_ROOT: $WINDSOR_PROJECT_ROOT" + + echo "=== DEBUG: Running windsor init ===" windsor init local --set dns.enabled=false --reset + + echo "=== DEBUG: Checking windsor.yaml after init ===" + echo "windsor.yaml content:" + cat contexts/local/windsor.yaml + echo "" + + echo "=== DEBUG: Checking if DNS is disabled ===" + if grep -q "dns:" contexts/local/windsor.yaml; then + echo "DNS section found in windsor.yaml:" + grep -A 5 "dns:" contexts/local/windsor.yaml + else + echo "No DNS section found in windsor.yaml" + fi + + echo "=== DEBUG: Checking docker-compose.yaml after init ===" + if [ -f .windsor/docker-compose.yaml ]; then + echo "docker-compose.yaml exists, checking for DNS services:" + if grep -q "dns:" .windsor/docker-compose.yaml; then + echo "WARNING: DNS service found in docker-compose.yaml after init!" + grep -A 10 -B 2 "dns:" .windsor/docker-compose.yaml + else + echo "No DNS service found in docker-compose.yaml after init" + fi + echo "Services in docker-compose.yaml:" + grep "container_name:" .windsor/docker-compose.yaml || echo "No container_name found" + else + echo "docker-compose.yaml does not exist after init" + fi + + echo "=== DEBUG: Running windsor up ===" windsor up --install --verbose --wait + + echo "=== DEBUG: Checking docker-compose.yaml after windsor up ===" + if [ -f .windsor/docker-compose.yaml ]; then + echo "docker-compose.yaml exists after windsor up, checking for DNS services:" + if grep -q "dns:" .windsor/docker-compose.yaml; then + echo "ERROR: DNS service found in docker-compose.yaml after windsor up!" + grep -A 10 -B 2 "dns:" .windsor/docker-compose.yaml + else + echo "No DNS service found in docker-compose.yaml after windsor up" + fi + echo "All services in docker-compose.yaml:" + grep "container_name:" .windsor/docker-compose.yaml || echo "No container_name found" + echo "File size: $(wc -l < .windsor/docker-compose.yaml) lines" + else + echo "docker-compose.yaml does not exist after windsor up" + fi + + echo "=== DEBUG: Checking IP addresses ===" + if [ -f .windsor/docker-compose.yaml ]; then + echo "IP addresses in docker-compose.yaml:" + grep -A 1 "ipv4_address:" .windsor/docker-compose.yaml || echo "No ipv4_address found" + fi + + echo "=== DEBUG: Windsor Up process complete ===" - name: Collect Windsor State if: always() From 57d85976409d86da0c0d1d25aef107383c8c82ff Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:17:11 -0400 Subject: [PATCH 08/11] debug Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/workflows/ci.yaml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c7c61ba7..25191bc2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -227,6 +227,17 @@ jobs: - name: Collect Talos Diagnostics if: always() run: | + echo "=== DEBUG: Before talosctl installation ===" + echo "Checking docker-compose.yaml before talosctl installation:" + if [ -f .windsor/docker-compose.yaml ]; then + if grep -q "dns:" .windsor/docker-compose.yaml; then + echo "WARNING: DNS service found before talosctl installation!" + grep -A 5 -B 2 "dns:" .windsor/docker-compose.yaml + else + echo "No DNS service found before talosctl installation" + fi + fi + # Install talosctl if not available if ! command -v talosctl &> /dev/null; then echo "Installing talosctl..." @@ -238,8 +249,34 @@ jobs: rm -rf "$OLDPWD" fi + echo "=== DEBUG: Before windsor exec ===" + echo "Checking docker-compose.yaml before windsor exec:" + if [ -f .windsor/docker-compose.yaml ]; then + if grep -q "dns:" .windsor/docker-compose.yaml; then + echo "WARNING: DNS service found before windsor exec!" + grep -A 5 -B 2 "dns:" .windsor/docker-compose.yaml + else + echo "No DNS service found before windsor exec" + fi + echo "File timestamp: $(stat -c %y .windsor/docker-compose.yaml 2>/dev/null || stat -f %Sm .windsor/docker-compose.yaml 2>/dev/null || echo 'unknown')" + fi + # Run talos diagnostics collection within Windsor environment + echo "=== DEBUG: Running windsor exec ===" windsor exec -- .github/scripts/collect-talos-diagnostics.sh support-bundles/talos-diagnostics + + echo "=== DEBUG: After windsor exec ===" + echo "Checking docker-compose.yaml after windsor exec:" + if [ -f .windsor/docker-compose.yaml ]; then + if grep -q "dns:" .windsor/docker-compose.yaml; then + echo "ERROR: DNS service found after windsor exec!" + grep -A 10 -B 2 "dns:" .windsor/docker-compose.yaml + else + echo "No DNS service found after windsor exec" + fi + echo "File timestamp: $(stat -c %y .windsor/docker-compose.yaml 2>/dev/null || stat -f %Sm .windsor/docker-compose.yaml 2>/dev/null || echo 'unknown')" + echo "File size: $(wc -l < .windsor/docker-compose.yaml) lines" + fi - name: Collect support bundle if: always() From 18d873f2e82ad35ff9438e88da102d7110b88121 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:17:38 -0400 Subject: [PATCH 09/11] debug Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/workflows/ci.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 25191bc2..0cbdbc0c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -160,9 +160,15 @@ jobs: echo "=== DEBUG: Starting Windsor Up process ===" echo "Current working directory: $(pwd)" echo "WINDSOR_PROJECT_ROOT: $WINDSOR_PROJECT_ROOT" + echo "Windsor CLI version:" + windsor --version || echo "Failed to get Windsor version" + echo "Environment variables:" + env | grep -E "(WINDSOR|DOCKER)" | sort || echo "No Windsor/Docker env vars found" echo "=== DEBUG: Running windsor init ===" + echo "Command: windsor init local --set dns.enabled=false --reset" windsor init local --set dns.enabled=false --reset + echo "windsor init exit code: $?" echo "=== DEBUG: Checking windsor.yaml after init ===" echo "windsor.yaml content:" @@ -193,7 +199,9 @@ jobs: fi echo "=== DEBUG: Running windsor up ===" + echo "Command: windsor up --install --verbose --wait" windsor up --install --verbose --wait + echo "windsor up exit code: $?" echo "=== DEBUG: Checking docker-compose.yaml after windsor up ===" if [ -f .windsor/docker-compose.yaml ]; then From 967df4961600e8b07eba4b680de8d77e755b6379 Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:23:21 -0400 Subject: [PATCH 10/11] Enforce regeneration of windsor.yaml Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/workflows/ci.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0cbdbc0c..2953d8df 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -165,6 +165,27 @@ jobs: echo "Environment variables:" env | grep -E "(WINDSOR|DOCKER)" | sort || echo "No Windsor/Docker env vars found" + echo "=== DEBUG: Cleaning up existing configuration ===" + echo "Removing existing windsor.yaml if it exists:" + if [ -f contexts/local/windsor.yaml ]; then + echo "Found existing windsor.yaml, removing it:" + cat contexts/local/windsor.yaml + rm -f contexts/local/windsor.yaml + echo "windsor.yaml removed" + else + echo "No existing windsor.yaml found" + fi + + echo "Removing existing .windsor directory if it exists:" + if [ -d .windsor ]; then + echo "Found existing .windsor directory, removing it:" + ls -la .windsor/ + rm -rf .windsor + echo ".windsor directory removed" + else + echo "No existing .windsor directory found" + fi + echo "=== DEBUG: Running windsor init ===" echo "Command: windsor init local --set dns.enabled=false --reset" windsor init local --set dns.enabled=false --reset From 08769406ef9b0de610bb8f9990d090ba3316888b Mon Sep 17 00:00:00 2001 From: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> Date: Wed, 29 Oct 2025 09:31:21 -0400 Subject: [PATCH 11/11] Remove debug statements Signed-off-by: Ryan VanGundy <85766511+rmvangun@users.noreply.github.com> --- .github/workflows/ci.yaml | 128 ++------------------------------------ 1 file changed, 4 insertions(+), 124 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2953d8df..9538b3b7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -157,96 +157,13 @@ jobs: - name: Windsor Up run: | - echo "=== DEBUG: Starting Windsor Up process ===" - echo "Current working directory: $(pwd)" - echo "WINDSOR_PROJECT_ROOT: $WINDSOR_PROJECT_ROOT" - echo "Windsor CLI version:" - windsor --version || echo "Failed to get Windsor version" - echo "Environment variables:" - env | grep -E "(WINDSOR|DOCKER)" | sort || echo "No Windsor/Docker env vars found" + # Clean up any existing configuration + rm -f contexts/local/windsor.yaml + rm -rf .windsor - echo "=== DEBUG: Cleaning up existing configuration ===" - echo "Removing existing windsor.yaml if it exists:" - if [ -f contexts/local/windsor.yaml ]; then - echo "Found existing windsor.yaml, removing it:" - cat contexts/local/windsor.yaml - rm -f contexts/local/windsor.yaml - echo "windsor.yaml removed" - else - echo "No existing windsor.yaml found" - fi - - echo "Removing existing .windsor directory if it exists:" - if [ -d .windsor ]; then - echo "Found existing .windsor directory, removing it:" - ls -la .windsor/ - rm -rf .windsor - echo ".windsor directory removed" - else - echo "No existing .windsor directory found" - fi - - echo "=== DEBUG: Running windsor init ===" - echo "Command: windsor init local --set dns.enabled=false --reset" + # Initialize and start Windsor windsor init local --set dns.enabled=false --reset - echo "windsor init exit code: $?" - - echo "=== DEBUG: Checking windsor.yaml after init ===" - echo "windsor.yaml content:" - cat contexts/local/windsor.yaml - echo "" - - echo "=== DEBUG: Checking if DNS is disabled ===" - if grep -q "dns:" contexts/local/windsor.yaml; then - echo "DNS section found in windsor.yaml:" - grep -A 5 "dns:" contexts/local/windsor.yaml - else - echo "No DNS section found in windsor.yaml" - fi - - echo "=== DEBUG: Checking docker-compose.yaml after init ===" - if [ -f .windsor/docker-compose.yaml ]; then - echo "docker-compose.yaml exists, checking for DNS services:" - if grep -q "dns:" .windsor/docker-compose.yaml; then - echo "WARNING: DNS service found in docker-compose.yaml after init!" - grep -A 10 -B 2 "dns:" .windsor/docker-compose.yaml - else - echo "No DNS service found in docker-compose.yaml after init" - fi - echo "Services in docker-compose.yaml:" - grep "container_name:" .windsor/docker-compose.yaml || echo "No container_name found" - else - echo "docker-compose.yaml does not exist after init" - fi - - echo "=== DEBUG: Running windsor up ===" - echo "Command: windsor up --install --verbose --wait" windsor up --install --verbose --wait - echo "windsor up exit code: $?" - - echo "=== DEBUG: Checking docker-compose.yaml after windsor up ===" - if [ -f .windsor/docker-compose.yaml ]; then - echo "docker-compose.yaml exists after windsor up, checking for DNS services:" - if grep -q "dns:" .windsor/docker-compose.yaml; then - echo "ERROR: DNS service found in docker-compose.yaml after windsor up!" - grep -A 10 -B 2 "dns:" .windsor/docker-compose.yaml - else - echo "No DNS service found in docker-compose.yaml after windsor up" - fi - echo "All services in docker-compose.yaml:" - grep "container_name:" .windsor/docker-compose.yaml || echo "No container_name found" - echo "File size: $(wc -l < .windsor/docker-compose.yaml) lines" - else - echo "docker-compose.yaml does not exist after windsor up" - fi - - echo "=== DEBUG: Checking IP addresses ===" - if [ -f .windsor/docker-compose.yaml ]; then - echo "IP addresses in docker-compose.yaml:" - grep -A 1 "ipv4_address:" .windsor/docker-compose.yaml || echo "No ipv4_address found" - fi - - echo "=== DEBUG: Windsor Up process complete ===" - name: Collect Windsor State if: always() @@ -256,17 +173,6 @@ jobs: - name: Collect Talos Diagnostics if: always() run: | - echo "=== DEBUG: Before talosctl installation ===" - echo "Checking docker-compose.yaml before talosctl installation:" - if [ -f .windsor/docker-compose.yaml ]; then - if grep -q "dns:" .windsor/docker-compose.yaml; then - echo "WARNING: DNS service found before talosctl installation!" - grep -A 5 -B 2 "dns:" .windsor/docker-compose.yaml - else - echo "No DNS service found before talosctl installation" - fi - fi - # Install talosctl if not available if ! command -v talosctl &> /dev/null; then echo "Installing talosctl..." @@ -278,34 +184,8 @@ jobs: rm -rf "$OLDPWD" fi - echo "=== DEBUG: Before windsor exec ===" - echo "Checking docker-compose.yaml before windsor exec:" - if [ -f .windsor/docker-compose.yaml ]; then - if grep -q "dns:" .windsor/docker-compose.yaml; then - echo "WARNING: DNS service found before windsor exec!" - grep -A 5 -B 2 "dns:" .windsor/docker-compose.yaml - else - echo "No DNS service found before windsor exec" - fi - echo "File timestamp: $(stat -c %y .windsor/docker-compose.yaml 2>/dev/null || stat -f %Sm .windsor/docker-compose.yaml 2>/dev/null || echo 'unknown')" - fi - # Run talos diagnostics collection within Windsor environment - echo "=== DEBUG: Running windsor exec ===" windsor exec -- .github/scripts/collect-talos-diagnostics.sh support-bundles/talos-diagnostics - - echo "=== DEBUG: After windsor exec ===" - echo "Checking docker-compose.yaml after windsor exec:" - if [ -f .windsor/docker-compose.yaml ]; then - if grep -q "dns:" .windsor/docker-compose.yaml; then - echo "ERROR: DNS service found after windsor exec!" - grep -A 10 -B 2 "dns:" .windsor/docker-compose.yaml - else - echo "No DNS service found after windsor exec" - fi - echo "File timestamp: $(stat -c %y .windsor/docker-compose.yaml 2>/dev/null || stat -f %Sm .windsor/docker-compose.yaml 2>/dev/null || echo 'unknown')" - echo "File size: $(wc -l < .windsor/docker-compose.yaml) lines" - fi - name: Collect support bundle if: always()