## Summary
Replace the existing source-context integration lane with backend runtime black-box integration checks that run against started deployable containers.
This change wires deployable backend image references (both commit tag and immutable digest) from the build workflow into the tests workflow, then validates runtime behavior over network endpoints.
## Why
Integration confidence should come from testing running service artifacts, not only source-mounted or in-process execution.
## What Changed
- Build workflow now:
- Publishes deployable backend image tag reference and digest reference
- Exposes both as job outputs
- Passes both references into CICD Tests dispatch inputs
- CICD Tests workflow now:
- Accepts deployable backend tag and digest inputs
- Propagates these through setup outputs
- Replaces previous integration lane behavior with runtime black-box execution:
- Starts isolated Docker network
- Starts Postgres container
- Starts backend container from digest-pinned deployable image
- Enforces tag-to-digest consistency before running checks
- Runs endpoint checks against live container:
- GET /
- GET /compatibility
- GET /health
- Captures backend/db logs and container state on failure
- Cleans up containers and network via trap
- Documentation updated:
- Runtime contract enforcement section now includes runtime black-box integration checks
- CI success summary now reflects runtime integration lane behavior
## Scope
Included:
- Backend runtime black-box integration replacement for the existing integration lane
- Digest + tag identity enforcement
- Failure diagnostics for triage
Out of scope:
- Frontend runtime smoke checks
- E2E lane redesign
## Acceptance Criteria Mapping
- Integration tests execute against runtime container endpoints: ✅
- Integration lane consumes built image references (not source-mounted execution): ✅
- Failures surface service logs and test logs for triage: ✅
## Verification
- Workflow files pass local validation checks
- Pre-commit hooks pass on committed changes
- Branch pushed and ready for PR review
## Related
- Issue: #61
- Dependency context: #66
Co-authored-by: copilotcoder <copilotcoder@darkhelm.org>
Reviewed-on: #72
391 lines
15 KiB
YAML
391 lines
15 KiB
YAML
name: Renovate Dependency Updates
|
|
|
|
on:
|
|
schedule:
|
|
# Run Renovate every Monday at 8 AM UTC
|
|
- cron: '0 8 * * 1'
|
|
workflow_dispatch: # Allow manual triggering
|
|
inputs:
|
|
dry_run:
|
|
description: 'Run in dry-run mode (no changes made)'
|
|
required: false
|
|
default: 'false'
|
|
type: boolean
|
|
|
|
jobs:
|
|
renovate:
|
|
name: Renovate Dependencies
|
|
# Non-heavy workflow: allow any host exposing the generic ubuntu-act label.
|
|
runs-on: ubuntu-act
|
|
timeout-minutes: 90
|
|
|
|
steps:
|
|
- name: Setup Node.js for Renovate
|
|
run: |
|
|
echo "=== Setting up Node.js 24 for Renovate ==="
|
|
|
|
# Check existing Node.js
|
|
if command -v node &> /dev/null; then
|
|
echo "Current Node.js version: $(node --version)"
|
|
fi
|
|
if command -v npm &> /dev/null; then
|
|
echo "Current npm version: $(npm --version)"
|
|
fi
|
|
|
|
# Aggressive cleanup of all Node.js/npm installations
|
|
echo "Performing complete Node.js cleanup..."
|
|
|
|
# Stop any Node.js processes
|
|
sudo pkill -f node || true
|
|
|
|
# Remove all package-managed Node.js installations
|
|
sudo apt-get remove -y --purge nodejs npm node || true
|
|
sudo apt-get autoremove -y --purge || true
|
|
|
|
# Remove all manual installations and caches
|
|
sudo rm -rf /usr/local/bin/node* /usr/local/bin/npm* || true
|
|
sudo rm -rf /usr/local/lib/node* /usr/local/include/node* || true
|
|
sudo rm -rf ~/.npm ~/.nvm ~/.node* || true
|
|
sudo rm -rf /root/.npm /root/.nvm /root/.node* || true
|
|
sudo rm -rf /usr/share/nodejs || true
|
|
sudo rm -rf /etc/apt/sources.list.d/nodesource.list* || true
|
|
|
|
# Clear npm environment variables that might conflict
|
|
unset npm_config_prefix npm_config_cache npm_config_globalconfig npm_config_init_module || true
|
|
|
|
echo "✓ Cleanup completed"
|
|
|
|
# Install Node.js 24 from NodeSource with error handling
|
|
echo "Installing Node.js 24..."
|
|
|
|
# Remove any existing NodeSource repository
|
|
sudo rm -f /etc/apt/sources.list.d/nodesource.list || true
|
|
|
|
# Add NodeSource repository
|
|
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
|
|
|
|
# Install with DEBIAN_FRONTEND to avoid interactive prompts
|
|
echo "Installing Node.js package..."
|
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nodejs
|
|
|
|
# Verify and fix installation
|
|
echo "=== Verifying Node.js Installation ==="
|
|
|
|
# Check Node.js
|
|
if command -v node &> /dev/null; then
|
|
NODE_VERSION=$(node --version)
|
|
echo "✓ Node.js installed: $NODE_VERSION"
|
|
else
|
|
echo "❌ Node.js installation failed"
|
|
exit 1
|
|
fi
|
|
|
|
# Check npm and fix if needed
|
|
if command -v npm &> /dev/null && npm --version &> /dev/null; then
|
|
NPM_VERSION=$(npm --version)
|
|
echo "✓ npm working: $NPM_VERSION"
|
|
else
|
|
echo "⚠️ npm not working properly, reinstalling..."
|
|
|
|
# Method 1: Try to fix npm with the bundled version
|
|
if [ -f "/usr/bin/node" ] && [ -f "/usr/lib/node_modules/npm/bin/npm-cli.js" ]; then
|
|
echo "Using bundled npm..."
|
|
sudo ln -sf /usr/lib/node_modules/npm/bin/npm-cli.js /usr/bin/npm || true
|
|
sudo chmod +x /usr/bin/npm || true
|
|
fi
|
|
|
|
# Method 2: If that doesn't work, reinstall npm manually
|
|
if ! npm --version &> /dev/null; then
|
|
echo "Manual npm installation..."
|
|
curl -L https://www.npmjs.com/install.sh | sudo sh
|
|
fi
|
|
|
|
# Method 3: Last resort - use npx to bootstrap npm
|
|
if ! npm --version &> /dev/null; then
|
|
echo "Using node to run npm directly..."
|
|
# Create npm wrapper script
|
|
echo '#!/bin/bash' | sudo tee /usr/bin/npm > /dev/null
|
|
echo 'exec /usr/bin/node /usr/lib/node_modules/npm/bin/npm-cli.js "$@"' | sudo tee -a /usr/bin/npm > /dev/null
|
|
sudo chmod +x /usr/bin/npm
|
|
fi
|
|
|
|
# Final verification
|
|
if npm --version &> /dev/null; then
|
|
echo "✓ npm recovered successfully: $(npm --version)"
|
|
else
|
|
echo "❌ npm recovery failed"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Test npm basic functionality
|
|
echo "Testing npm functionality..."
|
|
if npm config get registry &> /dev/null; then
|
|
echo "✓ npm configuration accessible"
|
|
else
|
|
echo "⚠️ npm configuration issues, but continuing..."
|
|
fi
|
|
|
|
# Check version compatibility for Renovate
|
|
NODE_VERSION=$(node --version | cut -d'v' -f2)
|
|
echo "=== Version Compatibility Check ==="
|
|
echo "Node.js version: $NODE_VERSION"
|
|
|
|
if [[ $(echo "$NODE_VERSION 24.10.0" | awk '{print ($1 >= $2)}') == 1 ]]; then
|
|
echo "✅ Node.js version $NODE_VERSION meets Renovate latest requirements"
|
|
echo "RENOVATE_VERSION=latest" >> $GITHUB_ENV
|
|
else
|
|
echo "⚠️ Node.js version $NODE_VERSION - will use compatible Renovate version"
|
|
echo "RENOVATE_VERSION=40.3.2" >> $GITHUB_ENV
|
|
fi
|
|
|
|
- name: Install Renovate
|
|
run: |
|
|
echo "=== Installing Renovate ==="
|
|
|
|
# Set npm configuration for memory efficiency
|
|
npm config set fund false
|
|
npm config set audit false
|
|
npm config set progress false
|
|
npm config set maxsockets 3
|
|
npm config set fetch-retry-mintimeout 2000
|
|
npm config set fetch-retry-maxtimeout 10000
|
|
|
|
# Use the version determined in previous step
|
|
echo "Installing Renovate version: $RENOVATE_VERSION"
|
|
|
|
# Check available memory
|
|
echo "Memory info:"
|
|
free -h || echo "free command not available"
|
|
|
|
# Install with retry logic and memory-efficient options
|
|
for i in 1 2 3; do
|
|
echo "Renovate installation attempt $i/3..."
|
|
|
|
# Clear npm cache and temp files
|
|
npm cache clean --force || true
|
|
rm -rf /tmp/npm-* || true
|
|
|
|
# Use memory-efficient installation with longer timeout
|
|
if NODE_OPTIONS="--max-old-space-size=2048" timeout 600 npm install -g "renovate@$RENOVATE_VERSION" \
|
|
--no-audit \
|
|
--no-fund \
|
|
--no-optional \
|
|
--production \
|
|
--maxsockets=3 \
|
|
--fetch-timeout=30000; then
|
|
echo "✓ Renovate installation successful on attempt $i"
|
|
break
|
|
else
|
|
exit_code=$?
|
|
echo "⚠️ Renovate installation attempt $i failed (exit code: $exit_code)"
|
|
|
|
# Check if it was killed due to memory
|
|
if [ $exit_code -eq 137 ] || [ $exit_code -eq 143 ]; then
|
|
echo " Process was killed (likely out of memory)"
|
|
fi
|
|
|
|
if [ $i -eq 3 ]; then
|
|
echo "❌ All Renovate installation attempts failed"
|
|
echo "Debugging information:"
|
|
echo "Node.js version: $(node --version)"
|
|
echo "npm version: $(npm --version)"
|
|
echo "Memory status:"
|
|
free -h || echo "Memory info unavailable"
|
|
echo "npm config:"
|
|
npm config list || echo "npm config failed"
|
|
exit 1
|
|
fi
|
|
echo "Waiting 30 seconds before retry..."
|
|
sleep 30
|
|
fi
|
|
done
|
|
|
|
# Verify Renovate installation
|
|
echo "✓ Renovate version: $(renovate --version)"
|
|
echo "✓ Renovate location: $(which renovate)"
|
|
|
|
- name: Configure Renovate for Gitea
|
|
run: |
|
|
echo "=== Configuring Renovate for Gitea ==="
|
|
|
|
# Renovate reads RENOVATE_TOKEN, RENOVATE_PLATFORM, RENOVATE_ENDPOINT,
|
|
# and RENOVATE_GIT_AUTHOR directly from the environment at runtime.
|
|
# Keeping the config file minimal avoids repeated migration warnings.
|
|
cat > renovate-config.js << 'EOF'
|
|
module.exports = {
|
|
platform: 'gitea',
|
|
endpoint: 'https://dogar.darkhelm.org',
|
|
gitAuthor: 'Renovate Bot <renovate@darkhelm.org>',
|
|
repositories: ['DarkHelm.org/plex-playlist'],
|
|
onboarding: false,
|
|
requireConfig: 'required',
|
|
extends: ['local>DarkHelm.org/plex-playlist'],
|
|
prConcurrentLimit: 3,
|
|
branchConcurrentLimit: 5,
|
|
};
|
|
EOF
|
|
|
|
echo "✓ Renovate configuration created"
|
|
|
|
- name: Run Renovate
|
|
env:
|
|
# Prefer dedicated Renovate token, then fall back to existing CI tokens.
|
|
RENOVATE_TOKEN_SECRET: ${{ secrets.RENOVATE_TOKEN }}
|
|
ACTIONS_TRIGGER_TOKEN: ${{ secrets.ACTIONS_TRIGGER_TOKEN }}
|
|
PACKAGE_ACCESS_TOKEN: ${{ secrets.PACKAGE_ACCESS_TOKEN }}
|
|
RENOVATE_DRY_RUN: ${{ inputs.dry_run }}
|
|
RENOVATE_CONFIG_FILE: renovate-config.js
|
|
RENOVATE_PLATFORM: gitea
|
|
RENOVATE_ENDPOINT: https://dogar.darkhelm.org
|
|
RENOVATE_ALLOW_INSECURE_TLS: "true"
|
|
LOG_LEVEL: info
|
|
run: |
|
|
echo "=== Running Renovate Bot ==="
|
|
TARGET_REPO="DarkHelm.org/plex-playlist"
|
|
TARGET_ORG="${TARGET_REPO%%/*}"
|
|
|
|
RENOVATE_ENDPOINT_EFFECTIVE="${RENOVATE_ENDPOINT%/}"
|
|
if [[ "${RENOVATE_ENDPOINT_EFFECTIVE}" == */api/v1 ]]; then
|
|
API_ENDPOINT="${RENOVATE_ENDPOINT_EFFECTIVE}"
|
|
RENOVATE_ENDPOINT_EFFECTIVE="${RENOVATE_ENDPOINT_EFFECTIVE%/api/v1}"
|
|
else
|
|
API_ENDPOINT="${RENOVATE_ENDPOINT_EFFECTIVE}/api/v1"
|
|
fi
|
|
|
|
export RENOVATE_ENDPOINT="${RENOVATE_ENDPOINT_EFFECTIVE}"
|
|
echo "Renovate endpoint: ${RENOVATE_ENDPOINT}"
|
|
echo "Preflight API endpoint: ${API_ENDPOINT}"
|
|
|
|
CURL_INSECURE_FLAG=""
|
|
if [ "${RENOVATE_ALLOW_INSECURE_TLS:-false}" = "true" ]; then
|
|
echo "⚠ Renovate insecure TLS mode enabled for self-signed certificate endpoint"
|
|
CURL_INSECURE_FLAG="--insecure"
|
|
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
fi
|
|
|
|
select_token_with_repo_access() {
|
|
for candidate_name in RENOVATE_TOKEN_SECRET ACTIONS_TRIGGER_TOKEN PACKAGE_ACCESS_TOKEN; do
|
|
candidate_value="${!candidate_name:-}"
|
|
if [ -z "${candidate_value}" ]; then
|
|
continue
|
|
fi
|
|
|
|
USER_STATUS=$(curl -sS -o /tmp/renovate-auth-check-user.json -w "%{http_code}" \
|
|
${CURL_INSECURE_FLAG} \
|
|
-H "Authorization: token ${candidate_value}" \
|
|
"${API_ENDPOINT}/user" || true)
|
|
|
|
REPO_STATUS=$(curl -sS -o /tmp/renovate-auth-check-repo.json -w "%{http_code}" \
|
|
${CURL_INSECURE_FLAG} \
|
|
-H "Authorization: token ${candidate_value}" \
|
|
"${API_ENDPOINT}/repos/${TARGET_REPO}" || true)
|
|
|
|
ORG_STATUS=$(curl -sS -o /tmp/renovate-auth-check-org.json -w "%{http_code}" \
|
|
${CURL_INSECURE_FLAG} \
|
|
-H "Authorization: token ${candidate_value}" \
|
|
"${API_ENDPOINT}/orgs/${TARGET_ORG}" || true)
|
|
|
|
if [ "${USER_STATUS}" = "200" ] && [ "${REPO_STATUS}" = "200" ] && [ "${ORG_STATUS}" = "200" ]; then
|
|
echo "${candidate_name}:${candidate_value}"
|
|
return 0
|
|
fi
|
|
|
|
echo "⚠ Token candidate ${candidate_name} rejected (user=${USER_STATUS}, repo=${REPO_STATUS}, org=${ORG_STATUS})"
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
if ! SELECTED_TOKEN_RESULT="$(select_token_with_repo_access)"; then
|
|
echo "❌ No token available for Renovate authentication with repository access"
|
|
echo "Configure RENOVATE_TOKEN with repo+issue write and organization/user read scopes."
|
|
echo "Token preflight checks attempted: ${API_ENDPOINT}/user, ${API_ENDPOINT}/repos/${TARGET_REPO}, and ${API_ENDPOINT}/orgs/${TARGET_ORG}"
|
|
if [ -s /tmp/renovate-auth-check-user.json ]; then
|
|
echo "Last user endpoint response body:"
|
|
cat /tmp/renovate-auth-check-user.json || true
|
|
fi
|
|
if [ -s /tmp/renovate-auth-check-repo.json ]; then
|
|
echo "Last repo endpoint response body:"
|
|
cat /tmp/renovate-auth-check-repo.json || true
|
|
fi
|
|
if [ -s /tmp/renovate-auth-check-org.json ]; then
|
|
echo "Last org endpoint response body:"
|
|
cat /tmp/renovate-auth-check-org.json || true
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
SELECTED_TOKEN_SOURCE="${SELECTED_TOKEN_RESULT%%:*}"
|
|
SELECTED_TOKEN="${SELECTED_TOKEN_RESULT#*:}"
|
|
export RENOVATE_TOKEN="${SELECTED_TOKEN}"
|
|
unset SELECTED_TOKEN RESULT_TOKEN
|
|
|
|
echo "✓ Renovate auth preflight passed with ${SELECTED_TOKEN_SOURCE}"
|
|
|
|
# Run Renovate with configuration
|
|
if [ "${RENOVATE_DRY_RUN}" = "true" ]; then
|
|
export RENOVATE_DRY_RUN="full"
|
|
echo "🔍 Running in DRY-RUN mode (no changes will be made)"
|
|
else
|
|
unset RENOVATE_DRY_RUN
|
|
fi
|
|
|
|
renovate --platform "${RENOVATE_PLATFORM}" --endpoint "${RENOVATE_ENDPOINT}" DarkHelm.org/plex-playlist
|
|
|
|
echo "✓ Renovate execution completed"
|
|
|
|
- name: Failure diagnostics
|
|
if: failure()
|
|
run: |
|
|
echo "=== Failure Diagnostics ==="
|
|
date -u '+timestamp_utc=%Y-%m-%dT%H:%M:%SZ'
|
|
echo "runner_name=${RUNNER_NAME:-unknown}"
|
|
echo "runner_hostname=${HOSTNAME:-unknown}"
|
|
uname -a || true
|
|
cat /etc/os-release 2>/dev/null || true
|
|
df -h || true
|
|
free -h || true
|
|
ps aux --sort=-%mem | head -n 30 || true
|
|
|
|
if command -v docker >/dev/null 2>&1; then
|
|
echo "=== Docker Diagnostics ==="
|
|
docker version || true
|
|
docker info || true
|
|
docker ps -a || true
|
|
docker images --digests | head -n 50 || true
|
|
else
|
|
echo "docker not available on this runner"
|
|
fi
|
|
|
|
echo "=== Kernel Tail ==="
|
|
dmesg | tail -n 120 || true
|
|
|
|
- name: Upload Renovate logs
|
|
if: always()
|
|
run: |
|
|
if [ -f "/tmp/renovate.log" ]; then
|
|
echo "=== Renovate Log Output ==="
|
|
echo "Last 50 lines of Renovate log:"
|
|
tail -50 /tmp/renovate.log
|
|
|
|
# Save log as artifact (if GitHub Actions artifact support exists)
|
|
mkdir -p /tmp/artifacts
|
|
cp /tmp/renovate.log /tmp/artifacts/renovate-$(date +%Y%m%d-%H%M%S).log
|
|
else
|
|
echo "No Renovate log file found"
|
|
fi
|
|
|
|
- name: Report Results
|
|
if: always()
|
|
run: |
|
|
echo "=== Renovate Execution Summary ==="
|
|
echo "Repository: DarkHelm.org/plex-playlist"
|
|
echo "Execution time: $(date)"
|
|
echo "Dry run mode: ${RENOVATE_DRY_RUN:-false}"
|
|
echo ""
|
|
echo "Check the Dependency Dashboard issue in your repository for detailed results:"
|
|
echo "https://dogar.darkhelm.org/DarkHelm.org/plex-playlist/issues"
|
|
echo ""
|
|
echo "Next scheduled run: Next Monday at 8 AM UTC"
|