fix: I dunno, I'm making git server changes
This commit is contained in:
448
.gitlab-ci.yml
448
.gitlab-ci.yml
@@ -1,32 +1,50 @@
|
||||
# MotoVaultPro GitLab CI/CD Pipeline
|
||||
# GitLab 18.6+ with shell executor
|
||||
# MotoVaultPro GitLab CI/CD Pipeline - Blue-Green Deployment
|
||||
# GitLab 18.6+ with separate build and production runners
|
||||
# See docs/CICD-DEPLOY.md for complete documentation
|
||||
# v1.6
|
||||
# v2.0 - Blue-Green with Auto-Rollback
|
||||
|
||||
stages:
|
||||
- validate
|
||||
- build
|
||||
- deploy
|
||||
- deploy-prepare
|
||||
- deploy-switch
|
||||
- verify
|
||||
- rollback
|
||||
- notify
|
||||
|
||||
variables:
|
||||
# Use stable clone path instead of runner-specific path
|
||||
GIT_CLONE_PATH: $CI_BUILDS_DIR/motovaultpro
|
||||
DEPLOY_PATH: $CI_BUILDS_DIR/motovaultpro
|
||||
DOCKER_COMPOSE_FILE: docker-compose.yml
|
||||
DOCKER_COMPOSE_PROD_FILE: docker-compose.prod.yml
|
||||
# Registry configuration
|
||||
REGISTRY: registry.motovaultpro.com
|
||||
REGISTRY_MIRRORS: ${REGISTRY}/mirrors
|
||||
IMAGE_TAG: ${CI_COMMIT_SHORT_SHA}
|
||||
BACKEND_IMAGE: ${REGISTRY}/motovaultpro/backend:${IMAGE_TAG}
|
||||
FRONTEND_IMAGE: ${REGISTRY}/motovaultpro/frontend:${IMAGE_TAG}
|
||||
|
||||
# Fix permissions after every job - docker creates files as root
|
||||
# Deployment configuration
|
||||
GIT_CLONE_PATH: ${CI_BUILDS_DIR}/motovaultpro
|
||||
DEPLOY_PATH: ${CI_BUILDS_DIR}/motovaultpro
|
||||
COMPOSE_FILE: docker-compose.yml
|
||||
COMPOSE_BLUE_GREEN: docker-compose.blue-green.yml
|
||||
|
||||
# Health check configuration
|
||||
HEALTH_CHECK_TIMEOUT: "60"
|
||||
|
||||
# Default after_script to fix permissions
|
||||
default:
|
||||
after_script:
|
||||
- echo "Fixing file permissions..."
|
||||
- sudo chown -R gitlab-runner:gitlab-runner "$DEPLOY_PATH" 2>/dev/null || true
|
||||
# Keep data directories owned by container user
|
||||
- sudo chown -R 1001:1001 "$DEPLOY_PATH/data/backups" "$DEPLOY_PATH/data/documents" 2>/dev/null || true
|
||||
|
||||
# Validate Stage - Check prerequisites
|
||||
# ============================================
|
||||
# Stage 1: VALIDATE
|
||||
# Check prerequisites before starting pipeline
|
||||
# ============================================
|
||||
validate:
|
||||
stage: validate
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
script:
|
||||
@@ -34,129 +52,385 @@ validate:
|
||||
- echo "Validating deployment prerequisites..."
|
||||
- echo "=========================================="
|
||||
- echo "Checking Docker..."
|
||||
- 'docker info > /dev/null 2>&1 || (echo "ERROR: Docker not accessible" && exit 1)'
|
||||
- echo "OK Docker is accessible"
|
||||
- docker info > /dev/null 2>&1 || (echo "ERROR - Docker not accessible" && exit 1)
|
||||
- echo "OK - Docker is accessible"
|
||||
- echo "Checking Docker Compose..."
|
||||
- 'docker compose version > /dev/null 2>&1 || (echo "ERROR: Docker Compose not available" && exit 1)'
|
||||
- echo "OK Docker Compose is available"
|
||||
- docker compose version > /dev/null 2>&1 || (echo "ERROR - Docker Compose not available" && exit 1)
|
||||
- echo "OK - Docker Compose is available"
|
||||
- echo "Checking deployment path..."
|
||||
- 'test -d "$DEPLOY_PATH" || (echo "ERROR: DEPLOY_PATH not found" && exit 1)'
|
||||
- echo "OK Deployment path exists"
|
||||
- test -d "$DEPLOY_PATH" || (echo "ERROR - DEPLOY_PATH not found" && exit 1)
|
||||
- echo "OK - Deployment path exists"
|
||||
- echo "Checking registry access..."
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$REGISTRY" || true
|
||||
- echo "OK - Registry authentication configured"
|
||||
- echo "Determining target stack..."
|
||||
- |
|
||||
STATE_FILE="$DEPLOY_PATH/config/deployment/state.json"
|
||||
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
||||
ACTIVE_STACK=$(jq -r '.active_stack // "blue"' "$STATE_FILE")
|
||||
if [ "$ACTIVE_STACK" = "blue" ]; then
|
||||
echo "TARGET_STACK=green" >> deploy.env
|
||||
else
|
||||
echo "TARGET_STACK=blue" >> deploy.env
|
||||
fi
|
||||
else
|
||||
echo "TARGET_STACK=green" >> deploy.env
|
||||
fi
|
||||
cat deploy.env
|
||||
- echo "=========================================="
|
||||
- echo "Validation complete"
|
||||
- echo "=========================================="
|
||||
artifacts:
|
||||
reports:
|
||||
dotenv: deploy.env
|
||||
|
||||
# Build Stage - Build Docker images
|
||||
# ============================================
|
||||
# Stage 2: BUILD
|
||||
# Build and push images to GitLab Container Registry
|
||||
# Runs on dedicated build server (shell executor)
|
||||
# ============================================
|
||||
build:
|
||||
stage: build
|
||||
tags:
|
||||
- build
|
||||
only:
|
||||
- main
|
||||
script:
|
||||
- echo "Authenticating with GitLab Container Registry..."
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$REGISTRY"
|
||||
- echo "=========================================="
|
||||
- echo "Building Docker images..."
|
||||
- echo "Commit - ${CI_COMMIT_SHORT_SHA}"
|
||||
- echo "Backend - ${BACKEND_IMAGE}"
|
||||
- echo "Frontend - ${FRONTEND_IMAGE}"
|
||||
- echo "=========================================="
|
||||
- cd "$DEPLOY_PATH"
|
||||
- echo "Building images..."
|
||||
- docker compose -f $DOCKER_COMPOSE_FILE build --no-cache
|
||||
|
||||
# Build backend
|
||||
- echo "Building backend..."
|
||||
- |
|
||||
docker build \
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||
--cache-from ${REGISTRY}/motovaultpro/backend:latest \
|
||||
-t ${BACKEND_IMAGE} \
|
||||
-t ${REGISTRY}/motovaultpro/backend:latest \
|
||||
-f backend/Dockerfile \
|
||||
.
|
||||
|
||||
# Build frontend
|
||||
- echo "Building frontend..."
|
||||
- |
|
||||
docker build \
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||
--build-arg VITE_AUTH0_DOMAIN=${VITE_AUTH0_DOMAIN:-motovaultpro.us.auth0.com} \
|
||||
--build-arg VITE_AUTH0_CLIENT_ID=${VITE_AUTH0_CLIENT_ID:-yspR8zdnSxmV8wFIghHynQ08iXAPoQJ3} \
|
||||
--build-arg VITE_AUTH0_AUDIENCE=${VITE_AUTH0_AUDIENCE:-https://api.motovaultpro.com} \
|
||||
--build-arg VITE_API_BASE_URL=/api \
|
||||
--cache-from ${REGISTRY}/motovaultpro/frontend:latest \
|
||||
-t ${FRONTEND_IMAGE} \
|
||||
-t ${REGISTRY}/motovaultpro/frontend:latest \
|
||||
-f frontend/Dockerfile \
|
||||
frontend
|
||||
|
||||
# Push images
|
||||
- echo "Pushing images to registry..."
|
||||
- docker push ${BACKEND_IMAGE}
|
||||
- docker push ${FRONTEND_IMAGE}
|
||||
- docker push ${REGISTRY}/motovaultpro/backend:latest
|
||||
- docker push ${REGISTRY}/motovaultpro/frontend:latest
|
||||
|
||||
- echo "=========================================="
|
||||
- echo "Build complete"
|
||||
- echo "=========================================="
|
||||
|
||||
# Deploy Stage - Inject secrets and deploy services
|
||||
deploy:
|
||||
stage: deploy
|
||||
# ============================================
|
||||
# Stage 3: DEPLOY-PREPARE
|
||||
# Pull images, start inactive stack, run health checks
|
||||
# ============================================
|
||||
deploy-prepare:
|
||||
stage: deploy-prepare
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
needs:
|
||||
- job: validate
|
||||
artifacts: true
|
||||
- job: build
|
||||
environment:
|
||||
name: production
|
||||
url: https://motovaultpro.com
|
||||
script:
|
||||
- echo "=========================================="
|
||||
- echo "Deploying MotoVaultPro..."
|
||||
- echo "Preparing deployment to ${TARGET_STACK} stack..."
|
||||
- echo "=========================================="
|
||||
- cd "$DEPLOY_PATH"
|
||||
- echo "Step 1/8 Initializing data directories..."
|
||||
|
||||
# Authenticate with registry
|
||||
- echo "Authenticating with registry..."
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$REGISTRY"
|
||||
|
||||
# Inject secrets
|
||||
- echo "Step 1/5 - Injecting secrets..."
|
||||
- chmod +x scripts/inject-secrets.sh
|
||||
- ./scripts/inject-secrets.sh
|
||||
|
||||
# Initialize data directories
|
||||
- echo "Step 2/5 - Initializing data directories..."
|
||||
- sudo mkdir -p data/backups data/documents
|
||||
- sudo chown -R 1001:1001 data/backups data/documents
|
||||
- sudo chmod 755 data/backups data/documents
|
||||
- echo "Step 2/8 Injecting secrets..."
|
||||
- chmod +x scripts/inject-secrets.sh
|
||||
- ./scripts/inject-secrets.sh
|
||||
- echo "Step 3/8 Stopping existing services..."
|
||||
- docker compose -f $DOCKER_COMPOSE_FILE -f $DOCKER_COMPOSE_PROD_FILE down --timeout 30 || true
|
||||
- echo "Step 4/8 Pulling base images..."
|
||||
- docker compose -f $DOCKER_COMPOSE_FILE pull
|
||||
- echo "Step 5/8 Starting database services..."
|
||||
- docker compose -f $DOCKER_COMPOSE_FILE -f $DOCKER_COMPOSE_PROD_FILE up -d mvp-postgres mvp-redis
|
||||
- echo "Waiting for database to be ready..."
|
||||
- sleep 15
|
||||
- echo "Step 6/8 Running database migrations..."
|
||||
- docker compose -f $DOCKER_COMPOSE_FILE run --rm mvp-backend npm run migrate || echo "Migration skipped"
|
||||
- echo "Step 7/8 Vehicle catalog data..."
|
||||
# Schema and data now loaded via standard migration system
|
||||
# Migration runner handles table creation and data loading automatically
|
||||
- echo "Vehicle catalog loaded via platform feature migration"
|
||||
- echo "Flushing Redis cache..."
|
||||
- docker exec mvp-redis redis-cli FLUSHALL
|
||||
- echo "Step 8/8 Starting all services..."
|
||||
- docker compose -f $DOCKER_COMPOSE_FILE -f $DOCKER_COMPOSE_PROD_FILE up -d
|
||||
- echo "Waiting for services to initialize..."
|
||||
- sleep 30
|
||||
|
||||
# Pull new images
|
||||
- echo "Step 3/5 - Pulling images..."
|
||||
- docker pull ${BACKEND_IMAGE}
|
||||
- docker pull ${FRONTEND_IMAGE}
|
||||
|
||||
# Start inactive stack
|
||||
- echo "Step 4/5 - Starting ${TARGET_STACK} stack..."
|
||||
- |
|
||||
export BACKEND_IMAGE=${BACKEND_IMAGE}
|
||||
export FRONTEND_IMAGE=${FRONTEND_IMAGE}
|
||||
docker compose -f $COMPOSE_FILE -f $COMPOSE_BLUE_GREEN up -d \
|
||||
mvp-frontend-${TARGET_STACK} mvp-backend-${TARGET_STACK}
|
||||
|
||||
# Wait for stack to be ready
|
||||
- echo "Step 5/5 - Waiting for stack health..."
|
||||
- sleep 10
|
||||
|
||||
# Run health check
|
||||
- echo "Running health check on ${TARGET_STACK} stack..."
|
||||
- chmod +x scripts/ci/health-check.sh
|
||||
- ./scripts/ci/health-check.sh ${TARGET_STACK} ${HEALTH_CHECK_TIMEOUT}
|
||||
|
||||
# Update state with deployment info
|
||||
- |
|
||||
STATE_FILE="config/deployment/state.json"
|
||||
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
jq --arg stack "$TARGET_STACK" \
|
||||
--arg commit "$CI_COMMIT_SHORT_SHA" \
|
||||
--arg ts "$TIMESTAMP" \
|
||||
'.[$stack].version = $commit | .[$stack].commit = $commit | .[$stack].deployed_at = $ts | .[$stack].healthy = true' \
|
||||
"$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
||||
fi
|
||||
|
||||
- echo "=========================================="
|
||||
- echo "Deployment complete"
|
||||
- echo "Deploy preparation complete"
|
||||
- echo "=========================================="
|
||||
|
||||
# Verify Stage - Health checks
|
||||
verify:
|
||||
stage: verify
|
||||
# ============================================
|
||||
# Stage 4: DEPLOY-SWITCH
|
||||
# Switch traffic to new stack
|
||||
# ============================================
|
||||
deploy-switch:
|
||||
stage: deploy-switch
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
needs:
|
||||
- job: validate
|
||||
artifacts: true
|
||||
- job: deploy-prepare
|
||||
script:
|
||||
- echo "=========================================="
|
||||
- echo "Verifying deployment..."
|
||||
- echo "Switching traffic to ${TARGET_STACK} stack..."
|
||||
- echo "=========================================="
|
||||
- cd "$DEPLOY_PATH"
|
||||
- echo "Checking container status..."
|
||||
|
||||
# Switch traffic
|
||||
- chmod +x scripts/ci/switch-traffic.sh
|
||||
- ./scripts/ci/switch-traffic.sh ${TARGET_STACK} instant
|
||||
|
||||
# Update state
|
||||
- |
|
||||
FAILED=0
|
||||
for service in mvp-traefik mvp-frontend mvp-backend mvp-postgres mvp-redis; do
|
||||
status=$(docker inspect --format='{{.State.Status}}' $service 2>/dev/null || echo "not found")
|
||||
if [ "$status" != "running" ]; then
|
||||
echo "ERROR: $service is not running (status: $status)"
|
||||
docker logs $service --tail 50 2>/dev/null || true
|
||||
FAILED=1
|
||||
else
|
||||
echo "OK: $service is running"
|
||||
fi
|
||||
done
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "One or more services failed to start"
|
||||
exit 1
|
||||
STATE_FILE="config/deployment/state.json"
|
||||
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
jq --arg commit "$CI_COMMIT_SHORT_SHA" \
|
||||
--arg ts "$TIMESTAMP" \
|
||||
'.last_deployment = $ts | .last_deployment_commit = $commit | .last_deployment_status = "success" | .rollback_available = true' \
|
||||
"$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
||||
fi
|
||||
- echo "Checking backend health..."
|
||||
|
||||
- echo "=========================================="
|
||||
- echo "Traffic switch complete"
|
||||
- echo "=========================================="
|
||||
|
||||
# ============================================
|
||||
# Stage 5: VERIFY
|
||||
# Production health verification after switch
|
||||
# ============================================
|
||||
verify:
|
||||
stage: verify
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
needs:
|
||||
- job: validate
|
||||
artifacts: true
|
||||
- job: deploy-switch
|
||||
script:
|
||||
- echo "=========================================="
|
||||
- echo "Verifying production deployment..."
|
||||
- echo "=========================================="
|
||||
- cd "$DEPLOY_PATH"
|
||||
|
||||
# Wait for Traefik to propagate routing
|
||||
- echo "Waiting for traffic routing to stabilize..."
|
||||
- sleep 5
|
||||
|
||||
# Verify via external endpoint
|
||||
- echo "Checking external endpoint..."
|
||||
- |
|
||||
HEALTH_OK=0
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if docker exec mvp-backend curl -sf http://localhost:3001/health > /dev/null 2>&1; then
|
||||
echo "OK: Backend health check passed"
|
||||
HEALTH_OK=1
|
||||
if curl -sf https://motovaultpro.com/api/health > /dev/null 2>&1; then
|
||||
echo "OK - External health check passed"
|
||||
break
|
||||
fi
|
||||
echo "Attempt $i/6: Backend not ready, waiting 10s..."
|
||||
if [ $i -eq 6 ]; then
|
||||
echo "ERROR - External health check failed after 6 attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "Attempt $i/6 - Waiting 10s..."
|
||||
sleep 10
|
||||
done
|
||||
if [ $HEALTH_OK -eq 0 ]; then
|
||||
echo "ERROR: Backend health check failed after 6 attempts"
|
||||
docker logs mvp-backend --tail 100
|
||||
exit 1
|
||||
fi
|
||||
- echo "Checking frontend..."
|
||||
|
||||
# Verify container status
|
||||
- echo "Checking container status..."
|
||||
- |
|
||||
if docker compose -f $DOCKER_COMPOSE_FILE exec -T mvp-frontend curl -sf http://localhost:3000 > /dev/null 2>&1; then
|
||||
echo "OK: Frontend is accessible"
|
||||
else
|
||||
echo "WARNING: Frontend check failed (might need Traefik routing)"
|
||||
fi
|
||||
for service in mvp-frontend-${TARGET_STACK} mvp-backend-${TARGET_STACK}; do
|
||||
status=$(docker inspect --format='{{.State.Status}}' $service 2>/dev/null || echo "not found")
|
||||
health=$(docker inspect --format='{{.State.Health.Status}}' $service 2>/dev/null || echo "unknown")
|
||||
if [ "$status" != "running" ] || [ "$health" != "healthy" ]; then
|
||||
echo "ERROR - $service is not healthy (status: $status, health: $health)"
|
||||
docker logs $service --tail 50 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "OK - $service is running and healthy"
|
||||
done
|
||||
|
||||
- echo "=========================================="
|
||||
- echo "Deployment verified successfully!"
|
||||
- echo "Version ${CI_COMMIT_SHORT_SHA} is now live"
|
||||
- echo "=========================================="
|
||||
|
||||
# ============================================
|
||||
# Stage 6: ROLLBACK (on failure)
|
||||
# Automatic rollback if verify stage fails
|
||||
# ============================================
|
||||
rollback:
|
||||
stage: rollback
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
when: on_failure
|
||||
needs:
|
||||
- job: validate
|
||||
artifacts: true
|
||||
- job: deploy-switch
|
||||
- job: verify
|
||||
script:
|
||||
- echo "=========================================="
|
||||
- echo "INITIATING AUTO-ROLLBACK"
|
||||
- echo "=========================================="
|
||||
- cd "$DEPLOY_PATH"
|
||||
|
||||
# Run rollback script
|
||||
- chmod +x scripts/ci/auto-rollback.sh
|
||||
- ./scripts/ci/auto-rollback.sh "Verify stage failed - automatic rollback"
|
||||
|
||||
# Update state
|
||||
- |
|
||||
STATE_FILE="config/deployment/state.json"
|
||||
if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
|
||||
jq '.last_deployment_status = "rolled_back"' "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
||||
fi
|
||||
|
||||
- echo "=========================================="
|
||||
- echo "Rollback complete"
|
||||
- echo "=========================================="
|
||||
|
||||
# ============================================
|
||||
# Stage 7: NOTIFY
|
||||
# Send deployment notifications
|
||||
# ============================================
|
||||
notify-success:
|
||||
stage: notify
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
needs:
|
||||
- job: verify
|
||||
script:
|
||||
- echo "Sending success notification..."
|
||||
- cd "$DEPLOY_PATH"
|
||||
- chmod +x scripts/ci/notify.sh
|
||||
- ./scripts/ci/notify.sh success "Version ${CI_COMMIT_SHORT_SHA} deployed successfully" ${CI_COMMIT_SHORT_SHA}
|
||||
|
||||
notify-failure:
|
||||
stage: notify
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
when: on_failure
|
||||
needs:
|
||||
- job: build
|
||||
optional: true
|
||||
- job: deploy-prepare
|
||||
optional: true
|
||||
- job: deploy-switch
|
||||
optional: true
|
||||
- job: verify
|
||||
optional: true
|
||||
script:
|
||||
- echo "Sending failure notification..."
|
||||
- cd "$DEPLOY_PATH"
|
||||
- chmod +x scripts/ci/notify.sh
|
||||
- ./scripts/ci/notify.sh failure "Deployment of ${CI_COMMIT_SHORT_SHA} failed" ${CI_COMMIT_SHORT_SHA}
|
||||
|
||||
# ============================================
|
||||
# Manual Jobs
|
||||
# ============================================
|
||||
|
||||
# Manual maintenance migration job
|
||||
maintenance-migration:
|
||||
stage: deploy-prepare
|
||||
tags:
|
||||
- production
|
||||
- shell
|
||||
only:
|
||||
- main
|
||||
when: manual
|
||||
script:
|
||||
- echo "=========================================="
|
||||
- echo "MAINTENANCE MODE MIGRATION"
|
||||
- echo "=========================================="
|
||||
- cd "$DEPLOY_PATH"
|
||||
- chmod +x scripts/ci/maintenance-migrate.sh
|
||||
- ./scripts/ci/maintenance-migrate.sh backup
|
||||
|
||||
# Mirror base images (scheduled or manual)
|
||||
mirror-images:
|
||||
stage: build
|
||||
tags:
|
||||
- build
|
||||
only:
|
||||
- schedules
|
||||
- web
|
||||
when: manual
|
||||
script:
|
||||
- echo "Mirroring base images to GitLab Container Registry..."
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$REGISTRY"
|
||||
- chmod +x scripts/ci/mirror-base-images.sh
|
||||
- REGISTRY=${REGISTRY}/mirrors ./scripts/ci/mirror-base-images.sh
|
||||
|
||||
Reference in New Issue
Block a user