feat: Add version tracking and maintenance monitoring (issue #15)
Complete implementation of automatic version tracking and drift detection: New Scripts: - scripts/collect-client-versions.sh: Query deployed versions from Docker - Connects via Ansible to running servers - Extracts versions from container images - Updates registry automatically - scripts/check-client-versions.sh: Compare versions across clients - Multiple formats: table (colorized), CSV, JSON - Filter by outdated versions - Highlights drift with color coding - scripts/detect-version-drift.sh: Identify version differences - Detects clients with outdated versions - Threshold-based staleness detection (default 30 days) - Actionable recommendations - Exit code 1 if drift detected (CI/monitoring friendly) Updated Scripts: - scripts/deploy-client.sh: Auto-collect versions after deployment - scripts/rebuild-client.sh: Auto-collect versions after rebuild Documentation: - docs/maintenance-tracking.md: Complete maintenance guide - Version management workflows - Security update procedures - Monitoring integration examples - Troubleshooting guide Features: ✅ Automatic version collection from deployed servers ✅ Multi-client version comparison reports ✅ Version drift detection with recommendations ✅ Integration with deployment workflows ✅ Export to CSV/JSON for external tools ✅ Canary-first update workflow support Usage Examples: ```bash # Collect versions ./scripts/collect-client-versions.sh dev # Compare all clients ./scripts/check-client-versions.sh # Detect drift ./scripts/detect-version-drift.sh # Export for monitoring ./scripts/check-client-versions.sh --format=json ``` Closes #15 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bf4659f662
commit
0c4d536246
6 changed files with 1108 additions and 0 deletions
477
docs/maintenance-tracking.md
Normal file
477
docs/maintenance-tracking.md
Normal file
|
|
@ -0,0 +1,477 @@
|
||||||
|
# Maintenance and Version Tracking
|
||||||
|
|
||||||
|
Comprehensive guide to tracking software versions, maintenance history, and detecting version drift across all deployed clients.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The infrastructure tracks:
|
||||||
|
- **Software versions** - Authentik, Nextcloud, Traefik, Ubuntu
|
||||||
|
- **Maintenance dates** - Last update, security patches, OS updates
|
||||||
|
- **Version drift** - Clients running different versions
|
||||||
|
- **Update history** - Audit trail of changes
|
||||||
|
|
||||||
|
All version and maintenance data is stored in [`clients/registry.yml`](../clients/registry.yml).
|
||||||
|
|
||||||
|
## Registry Structure
|
||||||
|
|
||||||
|
Each client tracks versions and maintenance:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
clients:
|
||||||
|
myclient:
|
||||||
|
versions:
|
||||||
|
authentik: "2025.10.3"
|
||||||
|
nextcloud: "30.0.17"
|
||||||
|
traefik: "v3.0"
|
||||||
|
ubuntu: "24.04"
|
||||||
|
|
||||||
|
maintenance:
|
||||||
|
last_full_update: 2026-01-17
|
||||||
|
last_security_patch: 2026-01-17
|
||||||
|
last_os_update: 2026-01-17
|
||||||
|
last_backup_verified: null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version Management Scripts
|
||||||
|
|
||||||
|
### Collect Client Versions
|
||||||
|
|
||||||
|
Query actual deployed versions from a running server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Collect versions from dev client
|
||||||
|
./scripts/collect-client-versions.sh dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This script:
|
||||||
|
- Connects to the server via Ansible
|
||||||
|
- Queries Docker container image tags
|
||||||
|
- Queries Ubuntu OS version
|
||||||
|
- Updates the registry automatically
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```
|
||||||
|
Collecting versions for client: dev
|
||||||
|
|
||||||
|
Querying deployed versions...
|
||||||
|
Collecting Docker container versions...
|
||||||
|
✓ Versions collected
|
||||||
|
|
||||||
|
Collected versions:
|
||||||
|
Authentik: 2025.10.3
|
||||||
|
Nextcloud: 30.0.17
|
||||||
|
Traefik: v3.0
|
||||||
|
Ubuntu: 24.04
|
||||||
|
|
||||||
|
✓ Registry updated
|
||||||
|
```
|
||||||
|
|
||||||
|
**Requirements:**
|
||||||
|
- Server must be deployed and reachable
|
||||||
|
- `HCLOUD_TOKEN` environment variable set
|
||||||
|
- Ansible configured with dynamic inventory
|
||||||
|
|
||||||
|
### Check All Client Versions
|
||||||
|
|
||||||
|
Compare versions across all clients:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Default: Table format with color coding
|
||||||
|
./scripts/check-client-versions.sh
|
||||||
|
|
||||||
|
# Export as CSV
|
||||||
|
./scripts/check-client-versions.sh --format=csv
|
||||||
|
|
||||||
|
# Export as JSON
|
||||||
|
./scripts/check-client-versions.sh --format=json
|
||||||
|
|
||||||
|
# Show only clients with outdated versions
|
||||||
|
./scripts/check-client-versions.sh --outdated
|
||||||
|
```
|
||||||
|
|
||||||
|
**Table output:**
|
||||||
|
```
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
CLIENT VERSION REPORT
|
||||||
|
═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
CLIENT STATUS AUTHENTIK NEXTCLOUD TRAEFIK UBUNTU
|
||||||
|
──────────────────────────────────────────────────────────────────────────────────────────────
|
||||||
|
dev deployed 2025.10.3 30.0.17 v3.0 24.04
|
||||||
|
client1 deployed 2025.10.2 30.0.16 v3.0 24.04
|
||||||
|
|
||||||
|
Latest versions:
|
||||||
|
Authentik: 2025.10.3
|
||||||
|
Nextcloud: 30.0.17
|
||||||
|
Traefik: v3.0
|
||||||
|
Ubuntu: 24.04
|
||||||
|
|
||||||
|
Note: Red indicates outdated version
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSV output:**
|
||||||
|
```csv
|
||||||
|
client,status,authentik,nextcloud,traefik,ubuntu,last_update,outdated
|
||||||
|
dev,deployed,2025.10.3,30.0.17,v3.0,24.04,2026-01-17,no
|
||||||
|
client1,deployed,2025.10.2,30.0.16,v3.0,24.04,2026-01-10,yes
|
||||||
|
```
|
||||||
|
|
||||||
|
**JSON output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"latest_versions": {
|
||||||
|
"authentik": "2025.10.3",
|
||||||
|
"nextcloud": "30.0.17",
|
||||||
|
"traefik": "v3.0",
|
||||||
|
"ubuntu": "24.04"
|
||||||
|
},
|
||||||
|
"clients": [
|
||||||
|
{
|
||||||
|
"name": "dev",
|
||||||
|
"status": "deployed",
|
||||||
|
"versions": {
|
||||||
|
"authentik": "2025.10.3",
|
||||||
|
"nextcloud": "30.0.17",
|
||||||
|
"traefik": "v3.0",
|
||||||
|
"ubuntu": "24.04"
|
||||||
|
},
|
||||||
|
"last_update": "2026-01-17",
|
||||||
|
"outdated": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Detect Version Drift
|
||||||
|
|
||||||
|
Identify clients with outdated versions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Default: Check all deployed clients
|
||||||
|
./scripts/detect-version-drift.sh
|
||||||
|
|
||||||
|
# Check clients not updated in 30+ days
|
||||||
|
./scripts/detect-version-drift.sh --threshold=30
|
||||||
|
|
||||||
|
# Check specific application only
|
||||||
|
./scripts/detect-version-drift.sh --app=authentik
|
||||||
|
|
||||||
|
# Summary output for monitoring
|
||||||
|
./scripts/detect-version-drift.sh --format=summary
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output when drift detected:**
|
||||||
|
```
|
||||||
|
⚠ VERSION DRIFT DETECTED
|
||||||
|
|
||||||
|
Clients with outdated versions:
|
||||||
|
|
||||||
|
• client1
|
||||||
|
Authentik: 2025.10.2 → 2025.10.3
|
||||||
|
Nextcloud: 30.0.16 → 30.0.17
|
||||||
|
|
||||||
|
• client2
|
||||||
|
Last update: 2025-12-15 (>30 days ago)
|
||||||
|
|
||||||
|
Recommended actions:
|
||||||
|
|
||||||
|
1. Test updates on canary server first:
|
||||||
|
./scripts/rebuild-client.sh dev
|
||||||
|
|
||||||
|
2. Verify canary health:
|
||||||
|
./scripts/client-status.sh dev
|
||||||
|
|
||||||
|
3. Update outdated clients:
|
||||||
|
./scripts/rebuild-client.sh client1
|
||||||
|
./scripts/rebuild-client.sh client2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exit codes:**
|
||||||
|
- `0` - No drift detected (all clients up to date)
|
||||||
|
- `1` - Drift detected (action needed)
|
||||||
|
- `2` - Error (script failure)
|
||||||
|
|
||||||
|
**Summary format** (useful for monitoring):
|
||||||
|
```
|
||||||
|
Status: DRIFT DETECTED
|
||||||
|
Drift: Yes
|
||||||
|
Clients checked: 5
|
||||||
|
Clients with outdated versions: 2
|
||||||
|
Clients not updated in 30 days: 1
|
||||||
|
Affected clients: client1 client2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automatic Version Collection
|
||||||
|
|
||||||
|
Version collection is **automatically performed** after deployments:
|
||||||
|
|
||||||
|
### On New Deployment
|
||||||
|
|
||||||
|
`./scripts/deploy-client.sh myclient`:
|
||||||
|
1. Provisions infrastructure
|
||||||
|
2. Deploys applications
|
||||||
|
3. Updates registry with server info
|
||||||
|
4. **Collects and records versions** ← Automatic
|
||||||
|
|
||||||
|
### On Rebuild
|
||||||
|
|
||||||
|
`./scripts/rebuild-client.sh myclient`:
|
||||||
|
1. Destroys old infrastructure
|
||||||
|
2. Provisions new infrastructure
|
||||||
|
3. Deploys applications
|
||||||
|
4. Updates registry
|
||||||
|
5. **Collects and records versions** ← Automatic
|
||||||
|
|
||||||
|
If automatic collection fails (server not ready, network issue):
|
||||||
|
```
|
||||||
|
⚠ Could not collect versions automatically
|
||||||
|
Run manually later: ./scripts/collect-client-versions.sh myclient
|
||||||
|
```
|
||||||
|
|
||||||
|
## Maintenance Workflows
|
||||||
|
|
||||||
|
### Security Update Workflow
|
||||||
|
|
||||||
|
1. **Check current state**
|
||||||
|
```bash
|
||||||
|
./scripts/check-client-versions.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update canary first** (dev server)
|
||||||
|
```bash
|
||||||
|
./scripts/rebuild-client.sh dev
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verify canary**
|
||||||
|
```bash
|
||||||
|
# Check health
|
||||||
|
./scripts/client-status.sh dev
|
||||||
|
|
||||||
|
# Verify versions updated
|
||||||
|
./scripts/collect-client-versions.sh dev
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Detect drift** (identify outdated clients)
|
||||||
|
```bash
|
||||||
|
./scripts/detect-version-drift.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Roll out to production**
|
||||||
|
```bash
|
||||||
|
# Update each client
|
||||||
|
./scripts/rebuild-client.sh client1
|
||||||
|
./scripts/rebuild-client.sh client2
|
||||||
|
|
||||||
|
# Or batch update (be careful!)
|
||||||
|
for client in $(./scripts/list-clients.sh --role=production --format=csv | tail -n +2 | cut -d, -f1); do
|
||||||
|
./scripts/rebuild-client.sh "$client"
|
||||||
|
sleep 300 # Wait 5 minutes between updates
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Verify all updated**
|
||||||
|
```bash
|
||||||
|
./scripts/detect-version-drift.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monthly Maintenance Check
|
||||||
|
|
||||||
|
Run these checks monthly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Version report
|
||||||
|
./scripts/check-client-versions.sh > reports/versions-$(date +%Y-%m).txt
|
||||||
|
|
||||||
|
# 2. Drift detection
|
||||||
|
./scripts/detect-version-drift.sh --threshold=30
|
||||||
|
|
||||||
|
# 3. Client health
|
||||||
|
for client in $(./scripts/list-clients.sh --status=deployed --format=csv | tail -n +2 | cut -d, -f1); do
|
||||||
|
./scripts/client-status.sh "$client"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Maintenance Dates
|
||||||
|
|
||||||
|
Deployment scripts automatically update `last_full_update`. For other maintenance:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# After security patches (OS level)
|
||||||
|
yq eval -i ".clients.myclient.maintenance.last_security_patch = \"$(date +%Y-%m-%d)\"" clients/registry.yml
|
||||||
|
|
||||||
|
# After OS updates
|
||||||
|
yq eval -i ".clients.myclient.maintenance.last_os_update = \"$(date +%Y-%m-%d)\"" clients/registry.yml
|
||||||
|
|
||||||
|
# After backup verification
|
||||||
|
yq eval -i ".clients.myclient.maintenance.last_backup_verified = \"$(date +%Y-%m-%d)\"" clients/registry.yml
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
git add clients/registry.yml
|
||||||
|
git commit -m "chore: Update maintenance dates"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Monitoring
|
||||||
|
|
||||||
|
### Continuous Drift Detection
|
||||||
|
|
||||||
|
Set up a cron job or CI pipeline:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# check-drift.sh - Run daily
|
||||||
|
|
||||||
|
cd /path/to/infrastructure
|
||||||
|
|
||||||
|
# Check for drift
|
||||||
|
if ! ./scripts/detect-version-drift.sh --format=summary; then
|
||||||
|
# Send alert (Slack, email, etc.)
|
||||||
|
./scripts/detect-version-drift.sh | mail -s "Version Drift Detected" ops@example.com
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Export for External Tools
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Export version data as JSON for monitoring tools
|
||||||
|
./scripts/check-client-versions.sh --format=json > /var/monitoring/client-versions.json
|
||||||
|
|
||||||
|
# Export drift status
|
||||||
|
./scripts/detect-version-drift.sh --format=summary > /var/monitoring/drift-status.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prometheus Metrics
|
||||||
|
|
||||||
|
Convert to Prometheus format:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# export-metrics.sh
|
||||||
|
|
||||||
|
# Count clients by drift status
|
||||||
|
total=$(./scripts/list-clients.sh --status=deployed --format=csv | tail -n +2 | wc -l)
|
||||||
|
outdated=$(./scripts/check-client-versions.sh --format=csv --outdated | tail -n +2 | wc -l)
|
||||||
|
uptodate=$((total - outdated))
|
||||||
|
|
||||||
|
echo "# HELP clients_total Total number of deployed clients"
|
||||||
|
echo "# TYPE clients_total gauge"
|
||||||
|
echo "clients_total $total"
|
||||||
|
|
||||||
|
echo "# HELP clients_outdated Number of clients with outdated versions"
|
||||||
|
echo "# TYPE clients_outdated gauge"
|
||||||
|
echo "clients_outdated $outdated"
|
||||||
|
|
||||||
|
echo "# HELP clients_uptodate Number of clients with latest versions"
|
||||||
|
echo "# TYPE clients_uptodate gauge"
|
||||||
|
echo "clients_uptodate $uptodate"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version Pinning
|
||||||
|
|
||||||
|
To prevent automatic updates, pin versions in Ansible roles:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# roles/authentik/defaults/main.yml
|
||||||
|
authentik_version: "2025.10.3" # Pinned version
|
||||||
|
|
||||||
|
# To update:
|
||||||
|
# 1. Change pinned version
|
||||||
|
# 2. Update canary: ./scripts/rebuild-client.sh dev
|
||||||
|
# 3. Verify and roll out
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Version Collection Fails
|
||||||
|
|
||||||
|
**Problem:** `collect-client-versions.sh` cannot reach server
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check server is deployed and running:
|
||||||
|
```bash
|
||||||
|
./scripts/client-status.sh myclient
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verify HCLOUD_TOKEN is set:
|
||||||
|
```bash
|
||||||
|
echo $HCLOUD_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Test Ansible connectivity:
|
||||||
|
```bash
|
||||||
|
cd ansible
|
||||||
|
ansible -i hcloud.yml myclient -m ping
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Check Docker containers are running:
|
||||||
|
```bash
|
||||||
|
ansible -i hcloud.yml myclient -m shell -a "docker ps"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Incorrect Version Reported
|
||||||
|
|
||||||
|
**Problem:** Registry shows wrong version
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Re-collect versions manually:
|
||||||
|
```bash
|
||||||
|
./scripts/collect-client-versions.sh myclient
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Verify Docker images:
|
||||||
|
```bash
|
||||||
|
ansible -i hcloud.yml myclient -m shell -a "docker images"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Check container inspect:
|
||||||
|
```bash
|
||||||
|
ansible -i hcloud.yml myclient -m shell -a "docker inspect authentik-server | jq '.[0].Config.Image'"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version Drift False Positives
|
||||||
|
|
||||||
|
**Problem:** Drift detected for canary with intentionally different version
|
||||||
|
|
||||||
|
**Solution:** Use `--app` filter to check specific applications:
|
||||||
|
```bash
|
||||||
|
# Check only production-critical apps
|
||||||
|
./scripts/detect-version-drift.sh --app=authentik
|
||||||
|
./scripts/detect-version-drift.sh --app=nextcloud
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always test on canary first**
|
||||||
|
- Update `dev` client before production
|
||||||
|
- Verify health before wider rollout
|
||||||
|
|
||||||
|
2. **Stagger production updates**
|
||||||
|
- Don't update all clients simultaneously
|
||||||
|
- Wait 5-10 minutes between updates
|
||||||
|
- Monitor each update for issues
|
||||||
|
|
||||||
|
3. **Track maintenance in registry**
|
||||||
|
- Keep `last_full_update` current
|
||||||
|
- Record `last_security_patch` dates
|
||||||
|
- Document backup verification
|
||||||
|
|
||||||
|
4. **Regular drift checks**
|
||||||
|
- Run weekly: `detect-version-drift.sh`
|
||||||
|
- Address drift within 7 days
|
||||||
|
- Maintain version consistency
|
||||||
|
|
||||||
|
5. **Document version changes**
|
||||||
|
- Add notes to registry when pinning versions
|
||||||
|
- Commit registry changes with descriptive messages
|
||||||
|
- Track major version upgrades separately
|
||||||
|
|
||||||
|
6. **Automate reporting**
|
||||||
|
- Export weekly version reports
|
||||||
|
- Alert on drift detection
|
||||||
|
- Dashboard for version overview
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Client Registry](client-registry.md) - Registry system overview
|
||||||
|
- [Deployment Guide](deployment.md) - Deployment procedures
|
||||||
|
- [SSH Key Management](ssh-key-management.md) - Security and access
|
||||||
251
scripts/check-client-versions.sh
Executable file
251
scripts/check-client-versions.sh
Executable file
|
|
@ -0,0 +1,251 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Report software versions across all clients
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/check-client-versions.sh [options]
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --format=table Show as colorized table (default)
|
||||||
|
# --format=csv Export as CSV
|
||||||
|
# --format=json Export as JSON
|
||||||
|
# --app=<name> Filter by application (authentik|nextcloud|traefik|ubuntu)
|
||||||
|
# --outdated Show only clients with outdated versions
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
REGISTRY_FILE="$PROJECT_ROOT/clients/registry.yml"
|
||||||
|
|
||||||
|
# Default options
|
||||||
|
FORMAT="table"
|
||||||
|
FILTER_APP=""
|
||||||
|
SHOW_OUTDATED=false
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--format=*)
|
||||||
|
FORMAT="${arg#*=}"
|
||||||
|
;;
|
||||||
|
--app=*)
|
||||||
|
FILTER_APP="${arg#*=}"
|
||||||
|
;;
|
||||||
|
--outdated)
|
||||||
|
SHOW_OUTDATED=true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $arg"
|
||||||
|
echo "Usage: $0 [--format=table|csv|json] [--app=<name>] [--outdated]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if yq is available
|
||||||
|
if ! command -v yq &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: 'yq' not found. Install with: brew install yq${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if registry exists
|
||||||
|
if [ ! -f "$REGISTRY_FILE" ]; then
|
||||||
|
echo -e "${RED}Error: Registry file not found: $REGISTRY_FILE${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get list of clients
|
||||||
|
CLIENTS=$(yq eval '.clients | keys | .[]' "$REGISTRY_FILE" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$CLIENTS" ]; then
|
||||||
|
echo -e "${YELLOW}No clients found in registry${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine latest versions (from canary/dev or most common)
|
||||||
|
declare -A LATEST_VERSIONS
|
||||||
|
LATEST_VERSIONS[authentik]=$(yq eval '.clients | to_entries | .[].value.versions.authentik' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
LATEST_VERSIONS[nextcloud]=$(yq eval '.clients | to_entries | .[].value.versions.nextcloud' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
LATEST_VERSIONS[traefik]=$(yq eval '.clients | to_entries | .[].value.versions.traefik' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
LATEST_VERSIONS[ubuntu]=$(yq eval '.clients | to_entries | .[].value.versions.ubuntu' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
|
||||||
|
# Function to check if version is outdated
|
||||||
|
is_outdated() {
|
||||||
|
local app=$1
|
||||||
|
local version=$2
|
||||||
|
local latest=${LATEST_VERSIONS[$app]}
|
||||||
|
|
||||||
|
if [ "$version" != "$latest" ] && [ "$version" != "null" ] && [ "$version" != "unknown" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case $FORMAT in
|
||||||
|
table)
|
||||||
|
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${BLUE} CLIENT VERSION REPORT${NC}"
|
||||||
|
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Header
|
||||||
|
printf "${CYAN}%-15s %-15s %-15s %-15s %-15s %-15s${NC}\n" \
|
||||||
|
"CLIENT" "STATUS" "AUTHENTIK" "NEXTCLOUD" "TRAEFIK" "UBUNTU"
|
||||||
|
echo -e "${CYAN}$(printf '─%.0s' {1..90})${NC}"
|
||||||
|
|
||||||
|
# Rows
|
||||||
|
for client in $CLIENTS; do
|
||||||
|
status=$(yq eval ".clients.\"$client\".status" "$REGISTRY_FILE")
|
||||||
|
authentik=$(yq eval ".clients.\"$client\".versions.authentik" "$REGISTRY_FILE")
|
||||||
|
nextcloud=$(yq eval ".clients.\"$client\".versions.nextcloud" "$REGISTRY_FILE")
|
||||||
|
traefik=$(yq eval ".clients.\"$client\".versions.traefik" "$REGISTRY_FILE")
|
||||||
|
ubuntu=$(yq eval ".clients.\"$client\".versions.ubuntu" "$REGISTRY_FILE")
|
||||||
|
|
||||||
|
# Skip if filtering by outdated and not outdated
|
||||||
|
if [ "$SHOW_OUTDATED" = true ]; then
|
||||||
|
has_outdated=false
|
||||||
|
is_outdated "authentik" "$authentik" && has_outdated=true
|
||||||
|
is_outdated "nextcloud" "$nextcloud" && has_outdated=true
|
||||||
|
is_outdated "traefik" "$traefik" && has_outdated=true
|
||||||
|
is_outdated "ubuntu" "$ubuntu" && has_outdated=true
|
||||||
|
|
||||||
|
if [ "$has_outdated" = false ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Colorize versions (red if outdated)
|
||||||
|
authentik_color=$NC
|
||||||
|
is_outdated "authentik" "$authentik" && authentik_color=$RED
|
||||||
|
|
||||||
|
nextcloud_color=$NC
|
||||||
|
is_outdated "nextcloud" "$nextcloud" && nextcloud_color=$RED
|
||||||
|
|
||||||
|
traefik_color=$NC
|
||||||
|
is_outdated "traefik" "$traefik" && traefik_color=$RED
|
||||||
|
|
||||||
|
ubuntu_color=$NC
|
||||||
|
is_outdated "ubuntu" "$ubuntu" && ubuntu_color=$RED
|
||||||
|
|
||||||
|
# Status color
|
||||||
|
status_color=$GREEN
|
||||||
|
[ "$status" != "deployed" ] && status_color=$YELLOW
|
||||||
|
|
||||||
|
printf "%-15s ${status_color}%-15s${NC} ${authentik_color}%-15s${NC} ${nextcloud_color}%-15s${NC} ${traefik_color}%-15s${NC} ${ubuntu_color}%-15s${NC}\n" \
|
||||||
|
"$client" "$status" "$authentik" "$nextcloud" "$traefik" "$ubuntu"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}Latest versions:${NC}"
|
||||||
|
echo " Authentik: ${LATEST_VERSIONS[authentik]}"
|
||||||
|
echo " Nextcloud: ${LATEST_VERSIONS[nextcloud]}"
|
||||||
|
echo " Traefik: ${LATEST_VERSIONS[traefik]}"
|
||||||
|
echo " Ubuntu: ${LATEST_VERSIONS[ubuntu]}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Note: ${RED}Red${NC} indicates outdated version${NC}"
|
||||||
|
echo ""
|
||||||
|
;;
|
||||||
|
|
||||||
|
csv)
|
||||||
|
# CSV header
|
||||||
|
echo "client,status,authentik,nextcloud,traefik,ubuntu,last_update,outdated"
|
||||||
|
|
||||||
|
# CSV rows
|
||||||
|
for client in $CLIENTS; do
|
||||||
|
status=$(yq eval ".clients.\"$client\".status" "$REGISTRY_FILE")
|
||||||
|
authentik=$(yq eval ".clients.\"$client\".versions.authentik" "$REGISTRY_FILE")
|
||||||
|
nextcloud=$(yq eval ".clients.\"$client\".versions.nextcloud" "$REGISTRY_FILE")
|
||||||
|
traefik=$(yq eval ".clients.\"$client\".versions.traefik" "$REGISTRY_FILE")
|
||||||
|
ubuntu=$(yq eval ".clients.\"$client\".versions.ubuntu" "$REGISTRY_FILE")
|
||||||
|
last_update=$(yq eval ".clients.\"$client\".maintenance.last_full_update" "$REGISTRY_FILE")
|
||||||
|
|
||||||
|
# Check if any version is outdated
|
||||||
|
outdated="no"
|
||||||
|
is_outdated "authentik" "$authentik" && outdated="yes"
|
||||||
|
is_outdated "nextcloud" "$nextcloud" && outdated="yes"
|
||||||
|
is_outdated "traefik" "$traefik" && outdated="yes"
|
||||||
|
is_outdated "ubuntu" "$ubuntu" && outdated="yes"
|
||||||
|
|
||||||
|
# Skip if filtering by outdated
|
||||||
|
if [ "$SHOW_OUTDATED" = true ] && [ "$outdated" = "no" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$client,$status,$authentik,$nextcloud,$traefik,$ubuntu,$last_update,$outdated"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
|
||||||
|
json)
|
||||||
|
# Build JSON array
|
||||||
|
echo "{"
|
||||||
|
echo " \"latest_versions\": {"
|
||||||
|
echo " \"authentik\": \"${LATEST_VERSIONS[authentik]}\","
|
||||||
|
echo " \"nextcloud\": \"${LATEST_VERSIONS[nextcloud]}\","
|
||||||
|
echo " \"traefik\": \"${LATEST_VERSIONS[traefik]}\","
|
||||||
|
echo " \"ubuntu\": \"${LATEST_VERSIONS[ubuntu]}\""
|
||||||
|
echo " },"
|
||||||
|
echo " \"clients\": ["
|
||||||
|
|
||||||
|
first=true
|
||||||
|
for client in $CLIENTS; do
|
||||||
|
status=$(yq eval ".clients.\"$client\".status" "$REGISTRY_FILE")
|
||||||
|
authentik=$(yq eval ".clients.\"$client\".versions.authentik" "$REGISTRY_FILE")
|
||||||
|
nextcloud=$(yq eval ".clients.\"$client\".versions.nextcloud" "$REGISTRY_FILE")
|
||||||
|
traefik=$(yq eval ".clients.\"$client\".versions.traefik" "$REGISTRY_FILE")
|
||||||
|
ubuntu=$(yq eval ".clients.\"$client\".versions.ubuntu" "$REGISTRY_FILE")
|
||||||
|
last_update=$(yq eval ".clients.\"$client\".maintenance.last_full_update" "$REGISTRY_FILE")
|
||||||
|
|
||||||
|
# Check if any version is outdated
|
||||||
|
outdated=false
|
||||||
|
is_outdated "authentik" "$authentik" && outdated=true
|
||||||
|
is_outdated "nextcloud" "$nextcloud" && outdated=true
|
||||||
|
is_outdated "traefik" "$traefik" && outdated=true
|
||||||
|
is_outdated "ubuntu" "$ubuntu" && outdated=true
|
||||||
|
|
||||||
|
# Skip if filtering by outdated
|
||||||
|
if [ "$SHOW_OUTDATED" = true ] && [ "$outdated" = false ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$first" = false ]; then
|
||||||
|
echo " ,"
|
||||||
|
fi
|
||||||
|
first=false
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"name": "$client",
|
||||||
|
"status": "$status",
|
||||||
|
"versions": {
|
||||||
|
"authentik": "$authentik",
|
||||||
|
"nextcloud": "$nextcloud",
|
||||||
|
"traefik": "$traefik",
|
||||||
|
"ubuntu": "$ubuntu"
|
||||||
|
},
|
||||||
|
"last_update": "$last_update",
|
||||||
|
"outdated": $outdated
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " ]"
|
||||||
|
echo "}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Error: Unknown format '$FORMAT'${NC}"
|
||||||
|
echo "Valid formats: table, csv, json"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
132
scripts/collect-client-versions.sh
Executable file
132
scripts/collect-client-versions.sh
Executable file
|
|
@ -0,0 +1,132 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Collect deployed software versions from a client and update registry
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/collect-client-versions.sh <client_name>
|
||||||
|
#
|
||||||
|
# Queries the deployed server for actual running versions:
|
||||||
|
# - Docker container image versions
|
||||||
|
# - Ubuntu OS version
|
||||||
|
# - Updates the client registry with collected versions
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
REGISTRY_FILE="$PROJECT_ROOT/clients/registry.yml"
|
||||||
|
|
||||||
|
# Check arguments
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo -e "${RED}Error: Client name required${NC}"
|
||||||
|
echo "Usage: $0 <client_name>"
|
||||||
|
echo ""
|
||||||
|
echo "Example: $0 dev"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CLIENT_NAME="$1"
|
||||||
|
|
||||||
|
# Check if yq is available
|
||||||
|
if ! command -v yq &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: 'yq' not found. Install with: brew install yq${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required environment variables
|
||||||
|
if [ -z "${HCLOUD_TOKEN:-}" ]; then
|
||||||
|
echo -e "${RED}Error: HCLOUD_TOKEN environment variable not set${NC}"
|
||||||
|
echo "Export your Hetzner Cloud API token:"
|
||||||
|
echo " export HCLOUD_TOKEN='your-token-here'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if registry exists
|
||||||
|
if [ ! -f "$REGISTRY_FILE" ]; then
|
||||||
|
echo -e "${RED}Error: Registry file not found: $REGISTRY_FILE${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if client exists in registry
|
||||||
|
if yq eval ".clients.\"$CLIENT_NAME\"" "$REGISTRY_FILE" | grep -q "null"; then
|
||||||
|
echo -e "${RED}Error: Client '$CLIENT_NAME' not found in registry${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Collecting versions for client: $CLIENT_NAME${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT/ansible"
|
||||||
|
|
||||||
|
# Check if server is reachable
|
||||||
|
if ! timeout 10 ~/.local/bin/ansible -i hcloud.yml "$CLIENT_NAME" -m ping -o &>/dev/null; then
|
||||||
|
echo -e "${RED}Error: Cannot reach server for client '$CLIENT_NAME'${NC}"
|
||||||
|
echo "Server may not be deployed or network is unreachable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Querying deployed versions...${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Query Docker container versions
|
||||||
|
echo "Collecting Docker container versions..."
|
||||||
|
|
||||||
|
# Function to extract version from image tag
|
||||||
|
extract_version() {
|
||||||
|
local image=$1
|
||||||
|
# Extract version after the colon, or return "latest"
|
||||||
|
if [[ $image == *":"* ]]; then
|
||||||
|
echo "$image" | awk -F: '{print $2}'
|
||||||
|
else
|
||||||
|
echo "latest"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect Authentik version
|
||||||
|
AUTHENTIK_IMAGE=$(~/.local/bin/ansible -i hcloud.yml "$CLIENT_NAME" -m shell -a "docker inspect authentik-server 2>/dev/null | jq -r '.[0].Config.Image' 2>/dev/null || echo 'unknown'" -o 2>/dev/null | tail -1 | awk '{print $NF}')
|
||||||
|
AUTHENTIK_VERSION=$(extract_version "$AUTHENTIK_IMAGE")
|
||||||
|
|
||||||
|
# Collect Nextcloud version
|
||||||
|
NEXTCLOUD_IMAGE=$(~/.local/bin/ansible -i hcloud.yml "$CLIENT_NAME" -m shell -a "docker inspect nextcloud 2>/dev/null | jq -r '.[0].Config.Image' 2>/dev/null || echo 'unknown'" -o 2>/dev/null | tail -1 | awk '{print $NF}')
|
||||||
|
NEXTCLOUD_VERSION=$(extract_version "$NEXTCLOUD_IMAGE")
|
||||||
|
|
||||||
|
# Collect Traefik version
|
||||||
|
TRAEFIK_IMAGE=$(~/.local/bin/ansible -i hcloud.yml "$CLIENT_NAME" -m shell -a "docker inspect traefik 2>/dev/null | jq -r '.[0].Config.Image' 2>/dev/null || echo 'unknown'" -o 2>/dev/null | tail -1 | awk '{print $NF}')
|
||||||
|
TRAEFIK_VERSION=$(extract_version "$TRAEFIK_IMAGE")
|
||||||
|
|
||||||
|
# Collect Ubuntu version
|
||||||
|
UBUNTU_VERSION=$(~/.local/bin/ansible -i hcloud.yml "$CLIENT_NAME" -m shell -a "lsb_release -rs 2>/dev/null || echo 'unknown'" -o 2>/dev/null | tail -1 | awk '{print $NF}')
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Versions collected${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Display collected versions
|
||||||
|
echo "Collected versions:"
|
||||||
|
echo " Authentik: $AUTHENTIK_VERSION"
|
||||||
|
echo " Nextcloud: $NEXTCLOUD_VERSION"
|
||||||
|
echo " Traefik: $TRAEFIK_VERSION"
|
||||||
|
echo " Ubuntu: $UBUNTU_VERSION"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Update registry
|
||||||
|
echo -e "${YELLOW}Updating registry...${NC}"
|
||||||
|
|
||||||
|
# Update versions in registry
|
||||||
|
yq eval -i ".clients.\"$CLIENT_NAME\".versions.authentik = \"$AUTHENTIK_VERSION\"" "$REGISTRY_FILE"
|
||||||
|
yq eval -i ".clients.\"$CLIENT_NAME\".versions.nextcloud = \"$NEXTCLOUD_VERSION\"" "$REGISTRY_FILE"
|
||||||
|
yq eval -i ".clients.\"$CLIENT_NAME\".versions.traefik = \"$TRAEFIK_VERSION\"" "$REGISTRY_FILE"
|
||||||
|
yq eval -i ".clients.\"$CLIENT_NAME\".versions.ubuntu = \"$UBUNTU_VERSION\"" "$REGISTRY_FILE"
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ Registry updated${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Updated: $REGISTRY_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "To view registry:"
|
||||||
|
echo " ./scripts/client-status.sh $CLIENT_NAME"
|
||||||
|
|
@ -211,6 +211,16 @@ echo ""
|
||||||
echo -e "${GREEN}✓ Registry updated${NC}"
|
echo -e "${GREEN}✓ Registry updated${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Collect deployed versions
|
||||||
|
echo -e "${YELLOW}Collecting deployed versions...${NC}"
|
||||||
|
|
||||||
|
"$SCRIPT_DIR/collect-client-versions.sh" "$CLIENT_NAME" 2>/dev/null || {
|
||||||
|
echo -e "${YELLOW}⚠ Could not collect versions automatically${NC}"
|
||||||
|
echo "Run manually later: ./scripts/collect-client-versions.sh $CLIENT_NAME"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Calculate duration
|
# Calculate duration
|
||||||
END_TIME=$(date +%s)
|
END_TIME=$(date +%s)
|
||||||
DURATION=$((END_TIME - START_TIME))
|
DURATION=$((END_TIME - START_TIME))
|
||||||
|
|
|
||||||
228
scripts/detect-version-drift.sh
Executable file
228
scripts/detect-version-drift.sh
Executable file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Detect version drift between clients
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/detect-version-drift.sh [options]
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --threshold=<days> Only report clients not updated in X days (default: 30)
|
||||||
|
# --app=<name> Check specific app only (authentik|nextcloud|traefik|ubuntu)
|
||||||
|
# --format=table Show as table (default)
|
||||||
|
# --format=summary Show summary only
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 - No drift detected
|
||||||
|
# 1 - Drift detected
|
||||||
|
# 2 - Error
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
REGISTRY_FILE="$PROJECT_ROOT/clients/registry.yml"
|
||||||
|
|
||||||
|
# Default options
|
||||||
|
THRESHOLD_DAYS=30
|
||||||
|
FILTER_APP=""
|
||||||
|
FORMAT="table"
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--threshold=*)
|
||||||
|
THRESHOLD_DAYS="${arg#*=}"
|
||||||
|
;;
|
||||||
|
--app=*)
|
||||||
|
FILTER_APP="${arg#*=}"
|
||||||
|
;;
|
||||||
|
--format=*)
|
||||||
|
FORMAT="${arg#*=}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $arg"
|
||||||
|
echo "Usage: $0 [--threshold=<days>] [--app=<name>] [--format=table|summary]"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if yq is available
|
||||||
|
if ! command -v yq &> /dev/null; then
|
||||||
|
echo -e "${RED}Error: 'yq' not found. Install with: brew install yq${NC}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if registry exists
|
||||||
|
if [ ! -f "$REGISTRY_FILE" ]; then
|
||||||
|
echo -e "${RED}Error: Registry file not found: $REGISTRY_FILE${NC}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get list of deployed clients only
|
||||||
|
CLIENTS=$(yq eval '.clients | to_entries | map(select(.value.status == "deployed")) | .[].key' "$REGISTRY_FILE" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -z "$CLIENTS" ]; then
|
||||||
|
echo -e "${YELLOW}No deployed clients found${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine latest versions
|
||||||
|
declare -A LATEST_VERSIONS
|
||||||
|
LATEST_VERSIONS[authentik]=$(yq eval '.clients | to_entries | .[].value.versions.authentik' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
LATEST_VERSIONS[nextcloud]=$(yq eval '.clients | to_entries | .[].value.versions.nextcloud' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
LATEST_VERSIONS[traefik]=$(yq eval '.clients | to_entries | .[].value.versions.traefik' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
LATEST_VERSIONS[ubuntu]=$(yq eval '.clients | to_entries | .[].value.versions.ubuntu' "$REGISTRY_FILE" | sort -V | tail -1)
|
||||||
|
|
||||||
|
# Calculate date threshold
|
||||||
|
if command -v gdate &> /dev/null; then
|
||||||
|
# macOS with GNU coreutils
|
||||||
|
THRESHOLD_DATE=$(gdate -d "$THRESHOLD_DAYS days ago" +%Y-%m-%d)
|
||||||
|
elif date --version &> /dev/null 2>&1; then
|
||||||
|
# GNU date (Linux)
|
||||||
|
THRESHOLD_DATE=$(date -d "$THRESHOLD_DAYS days ago" +%Y-%m-%d)
|
||||||
|
else
|
||||||
|
# BSD date (macOS default)
|
||||||
|
THRESHOLD_DATE=$(date -v-${THRESHOLD_DAYS}d +%Y-%m-%d)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Counters
|
||||||
|
DRIFT_FOUND=0
|
||||||
|
OUTDATED_COUNT=0
|
||||||
|
STALE_COUNT=0
|
||||||
|
|
||||||
|
# Arrays to store drift details
|
||||||
|
declare -a DRIFT_CLIENTS
|
||||||
|
declare -a DRIFT_DETAILS
|
||||||
|
|
||||||
|
# Analyze each client
|
||||||
|
for client in $CLIENTS; do
|
||||||
|
authentik=$(yq eval ".clients.\"$client\".versions.authentik" "$REGISTRY_FILE")
|
||||||
|
nextcloud=$(yq eval ".clients.\"$client\".versions.nextcloud" "$REGISTRY_FILE")
|
||||||
|
traefik=$(yq eval ".clients.\"$client\".versions.traefik" "$REGISTRY_FILE")
|
||||||
|
ubuntu=$(yq eval ".clients.\"$client\".versions.ubuntu" "$REGISTRY_FILE")
|
||||||
|
last_update=$(yq eval ".clients.\"$client\".maintenance.last_full_update" "$REGISTRY_FILE")
|
||||||
|
|
||||||
|
has_drift=false
|
||||||
|
drift_reasons=()
|
||||||
|
|
||||||
|
# Check version drift
|
||||||
|
if [ -z "$FILTER_APP" ] || [ "$FILTER_APP" = "authentik" ]; then
|
||||||
|
if [ "$authentik" != "${LATEST_VERSIONS[authentik]}" ] && [ "$authentik" != "null" ] && [ "$authentik" != "unknown" ]; then
|
||||||
|
has_drift=true
|
||||||
|
drift_reasons+=("Authentik: $authentik → ${LATEST_VERSIONS[authentik]}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$FILTER_APP" ] || [ "$FILTER_APP" = "nextcloud" ]; then
|
||||||
|
if [ "$nextcloud" != "${LATEST_VERSIONS[nextcloud]}" ] && [ "$nextcloud" != "null" ] && [ "$nextcloud" != "unknown" ]; then
|
||||||
|
has_drift=true
|
||||||
|
drift_reasons+=("Nextcloud: $nextcloud → ${LATEST_VERSIONS[nextcloud]}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$FILTER_APP" ] || [ "$FILTER_APP" = "traefik" ]; then
|
||||||
|
if [ "$traefik" != "${LATEST_VERSIONS[traefik]}" ] && [ "$traefik" != "null" ] && [ "$traefik" != "unknown" ]; then
|
||||||
|
has_drift=true
|
||||||
|
drift_reasons+=("Traefik: $traefik → ${LATEST_VERSIONS[traefik]}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$FILTER_APP" ] || [ "$FILTER_APP" = "ubuntu" ]; then
|
||||||
|
if [ "$ubuntu" != "${LATEST_VERSIONS[ubuntu]}" ] && [ "$ubuntu" != "null" ] && [ "$ubuntu" != "unknown" ]; then
|
||||||
|
has_drift=true
|
||||||
|
drift_reasons+=("Ubuntu: $ubuntu → ${LATEST_VERSIONS[ubuntu]}")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if update is stale (older than threshold)
|
||||||
|
is_stale=false
|
||||||
|
if [ "$last_update" != "null" ] && [ -n "$last_update" ]; then
|
||||||
|
if [[ "$last_update" < "$THRESHOLD_DATE" ]]; then
|
||||||
|
is_stale=true
|
||||||
|
drift_reasons+=("Last update: $last_update (>$THRESHOLD_DAYS days ago)")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Record drift
|
||||||
|
if [ "$has_drift" = true ] || [ "$is_stale" = true ]; then
|
||||||
|
DRIFT_FOUND=1
|
||||||
|
DRIFT_CLIENTS+=("$client")
|
||||||
|
DRIFT_DETAILS+=("$(IFS='; '; echo "${drift_reasons[*]}")")
|
||||||
|
|
||||||
|
[ "$has_drift" = true ] && ((OUTDATED_COUNT++)) || true
|
||||||
|
[ "$is_stale" = true ] && ((STALE_COUNT++)) || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Output results
|
||||||
|
case $FORMAT in
|
||||||
|
table)
|
||||||
|
if [ $DRIFT_FOUND -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✓ No version drift detected${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "All deployed clients are running latest versions:"
|
||||||
|
echo " Authentik: ${LATEST_VERSIONS[authentik]}"
|
||||||
|
echo " Nextcloud: ${LATEST_VERSIONS[nextcloud]}"
|
||||||
|
echo " Traefik: ${LATEST_VERSIONS[traefik]}"
|
||||||
|
echo " Ubuntu: ${LATEST_VERSIONS[ubuntu]}"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo -e "${RED}⚠ VERSION DRIFT DETECTED${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}Clients with outdated versions:${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for i in "${!DRIFT_CLIENTS[@]}"; do
|
||||||
|
client="${DRIFT_CLIENTS[$i]}"
|
||||||
|
details="${DRIFT_DETAILS[$i]}"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}• $client${NC}"
|
||||||
|
IFS=';' read -ra REASONS <<< "$details"
|
||||||
|
for reason in "${REASONS[@]}"; do
|
||||||
|
echo " $reason"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${CYAN}Recommended actions:${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "1. Test updates on canary server first:"
|
||||||
|
echo " ${BLUE}./scripts/rebuild-client.sh dev${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "2. Verify canary health:"
|
||||||
|
echo " ${BLUE}./scripts/client-status.sh dev${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "3. Update outdated clients:"
|
||||||
|
for client in "${DRIFT_CLIENTS[@]}"; do
|
||||||
|
echo " ${BLUE}./scripts/rebuild-client.sh $client${NC}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
summary)
|
||||||
|
if [ $DRIFT_FOUND -eq 0 ]; then
|
||||||
|
echo "Status: OK"
|
||||||
|
echo "Drift: No"
|
||||||
|
echo "Clients checked: $(echo "$CLIENTS" | wc -l | xargs)"
|
||||||
|
else
|
||||||
|
echo "Status: DRIFT DETECTED"
|
||||||
|
echo "Drift: Yes"
|
||||||
|
echo "Clients checked: $(echo "$CLIENTS" | wc -l | xargs)"
|
||||||
|
echo "Clients with outdated versions: $OUTDATED_COUNT"
|
||||||
|
echo "Clients not updated in $THRESHOLD_DAYS days: $STALE_COUNT"
|
||||||
|
echo "Affected clients: ${DRIFT_CLIENTS[*]}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit $DRIFT_FOUND
|
||||||
|
|
@ -212,6 +212,16 @@ echo ""
|
||||||
echo -e "${GREEN}✓ Registry updated${NC}"
|
echo -e "${GREEN}✓ Registry updated${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Collect deployed versions
|
||||||
|
echo -e "${YELLOW}Collecting deployed versions...${NC}"
|
||||||
|
|
||||||
|
"$SCRIPT_DIR/collect-client-versions.sh" "$CLIENT_NAME" 2>/dev/null || {
|
||||||
|
echo -e "${YELLOW}⚠ Could not collect versions automatically${NC}"
|
||||||
|
echo "Run manually later: ./scripts/collect-client-versions.sh $CLIENT_NAME"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Calculate duration
|
# Calculate duration
|
||||||
END_TIME=$(date +%s)
|
END_TIME=$(date +%s)
|
||||||
DURATION=$((END_TIME - START_TIME))
|
DURATION=$((END_TIME - START_TIME))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue