#!/bin/bash set -euo pipefail # Service Inventory Collector # Comprehensive discovery of all running services, containers, and configurations # Part of the Current State Discovery Framework SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DISCOVERY_DIR="${SCRIPT_DIR}/results" TIMESTAMP=$(date +%Y%m%d_%H%M%S) REPORT_FILE="${DISCOVERY_DIR}/service_inventory_${TIMESTAMP}.json" # Create discovery directory mkdir -p "$DISCOVERY_DIR" main() { echo "🔍 Starting service inventory collection..." # Initialize JSON report cat > "$REPORT_FILE" << 'EOF' { "discovery_metadata": { "timestamp": "", "hostname": "", "discovery_version": "1.0" }, "docker_services": {}, "system_services": {}, "web_services": {}, "databases": {}, "media_services": {}, "monitoring_services": {}, "configuration_files": {}, "custom_applications": {} } EOF collect_metadata collect_docker_services collect_system_services collect_web_services collect_databases collect_media_services collect_monitoring_services collect_configuration_files collect_custom_applications echo "✅ Service inventory complete: $REPORT_FILE" generate_summary } collect_metadata() { echo "📋 Collecting metadata..." jq --arg timestamp "$(date -Iseconds)" \ --arg hostname "$(hostname)" \ '.discovery_metadata.timestamp = $timestamp | .discovery_metadata.hostname = $hostname' \ "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_docker_services() { echo "🐳 Collecting Docker services..." local docker_services=$(cat << 'EOF' { "containers": [], "images": [], "networks": [], "volumes": [], "compose_files": [], "docker_info": {} } EOF ) if ! command -v docker &>/dev/null; then jq --argjson docker_services "$docker_services" '.docker_services = $docker_services' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" return fi # Docker containers local containers='[]' if docker info &>/dev/null; then while IFS= read -r line; do if [[ -n "$line" ]]; then local container_data=$(echo "$line" | jq -R 'split(",") | {id: .[0], name: .[1], image: .[2], status: .[3], ports: .[4], created: .[5]}') containers=$(echo "$containers" | jq ". + [$container_data]") fi done < <(docker ps -a --format "{{.ID}},{{.Names}},{{.Image}},{{.Status}},{{.Ports}},{{.CreatedAt}}" 2>/dev/null || echo "") fi # Docker images local images='[]' if docker info &>/dev/null; then while IFS= read -r line; do if [[ -n "$line" ]]; then local image_data=$(echo "$line" | jq -R 'split(",") | {repository: .[0], tag: .[1], id: .[2], created: .[3], size: .[4]}') images=$(echo "$images" | jq ". + [$image_data]") fi done < <(docker images --format "{{.Repository}},{{.Tag}},{{.ID}},{{.CreatedAt}},{{.Size}}" 2>/dev/null || echo "") fi # Docker networks local networks='[]' if docker info &>/dev/null; then while IFS= read -r line; do if [[ -n "$line" ]]; then local network_name=$(echo "$line" | awk '{print $1}') local network_inspect=$(docker network inspect "$network_name" 2>/dev/null | jq '.[0] | {name: .Name, driver: .Driver, scope: .Scope, subnet: (.IPAM.Config[0].Subnet // ""), gateway: (.IPAM.Config[0].Gateway // "")}') networks=$(echo "$networks" | jq ". + [$network_inspect]") fi done < <(docker network ls --format "{{.Name}}" 2>/dev/null | grep -v "^$" || echo "") fi # Docker volumes local volumes='[]' if docker info &>/dev/null; then while IFS= read -r line; do if [[ -n "$line" ]]; then local volume_name=$(echo "$line" | awk '{print $1}') local volume_inspect=$(docker volume inspect "$volume_name" 2>/dev/null | jq '.[0] | {name: .Name, driver: .Driver, mountpoint: .Mountpoint}') # Get volume size local mountpoint=$(echo "$volume_inspect" | jq -r '.mountpoint') local volume_size="unknown" if [[ -d "$mountpoint" ]]; then volume_size=$(du -sh "$mountpoint" 2>/dev/null | awk '{print $1}' || echo "unknown") fi volume_inspect=$(echo "$volume_inspect" | jq --arg size "$volume_size" '. + {size: $size}') volumes=$(echo "$volumes" | jq ". + [$volume_inspect]") fi done < <(docker volume ls --format "{{.Name}}" 2>/dev/null | grep -v "^$" || echo "") fi # Find Docker Compose files local compose_files='[]' local compose_locations=( "/opt" "/home" "/var/lib" "$HOME" "$(pwd)" ) for location in "${compose_locations[@]}"; do if [[ -d "$location" ]]; then while IFS= read -r compose_file; do if [[ -f "$compose_file" ]]; then local compose_info=$(jq -n --arg path "$compose_file" --arg size "$(wc -l < "$compose_file" 2>/dev/null || echo 0)" \ '{path: $path, lines: ($size | tonumber)}') compose_files=$(echo "$compose_files" | jq ". + [$compose_info]") fi done < <(find "$location" -name "docker-compose*.yml" -o -name "compose*.yml" 2>/dev/null | head -20) fi done # Docker info local docker_info='{}' if docker info &>/dev/null; then local docker_version=$(docker version --format '{{.Server.Version}}' 2>/dev/null || echo "unknown") local storage_driver=$(docker info --format '{{.Driver}}' 2>/dev/null || echo "unknown") local total_containers=$(docker info --format '{{.Containers}}' 2>/dev/null || echo "0") local running_containers=$(docker info --format '{{.ContainersRunning}}' 2>/dev/null || echo "0") docker_info=$(jq -n --arg version "$docker_version" \ --arg driver "$storage_driver" \ --arg total "$total_containers" \ --arg running "$running_containers" \ '{version: $version, storage_driver: $driver, total_containers: ($total | tonumber), running_containers: ($running | tonumber)}') fi docker_services=$(echo "$docker_services" | jq --argjson containers "$containers" \ --argjson images "$images" \ --argjson networks "$networks" \ --argjson volumes "$volumes" \ --argjson compose_files "$compose_files" \ --argjson docker_info "$docker_info" \ '.containers = $containers | .images = $images | .networks = $networks | .volumes = $volumes | .compose_files = $compose_files | .docker_info = $docker_info') jq --argjson docker_services "$docker_services" '.docker_services = $docker_services' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_system_services() { echo "⚙️ Collecting system services..." local system_services=$(cat << 'EOF' { "systemd_services": [], "cron_jobs": [], "startup_scripts": [], "background_processes": [] } EOF ) # Systemd services local systemd_services='[]' if command -v systemctl &>/dev/null; then while IFS= read -r line; do if [[ -n "$line" ]]; then local service_data=$(echo "$line" | jq -R 'split(" ") | {name: .[0], load: .[1], active: .[2], sub: .[3], description: (.[4:] | join(" "))}') systemd_services=$(echo "$systemd_services" | jq ". + [$service_data]") fi done < <(systemctl list-units --type=service --no-pager --no-legend --state=active | head -50) fi # Cron jobs local cron_jobs='[]' # System cron jobs if [[ -d /etc/cron.d ]]; then for cron_file in /etc/cron.d/*; do if [[ -f "$cron_file" ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[^#] && -n "$line" ]]; then local job_info=$(jq -n --arg file "$(basename "$cron_file")" --arg job "$line" \ '{source: $file, type: "system", job: $job}') cron_jobs=$(echo "$cron_jobs" | jq ". + [$job_info]") fi done < "$cron_file" fi done fi # User cron jobs if command -v crontab &>/dev/null; then local user_cron=$(crontab -l 2>/dev/null || echo "") if [[ -n "$user_cron" ]]; then while IFS= read -r line; do if [[ "$line" =~ ^[^#] && -n "$line" ]]; then local job_info=$(jq -n --arg user "$(whoami)" --arg job "$line" \ '{source: $user, type: "user", job: $job}') cron_jobs=$(echo "$cron_jobs" | jq ". + [$job_info]") fi done <<< "$user_cron" fi fi # Startup scripts local startup_scripts='[]' local startup_locations=("/etc/init.d" "/etc/systemd/system" "/home/*/.*profile" "/etc/profile.d") for location_pattern in "${startup_locations[@]}"; do for location in $location_pattern; do if [[ -d "$location" ]]; then while IFS= read -r script_file; do if [[ -f "$script_file" && -x "$script_file" ]]; then local script_info=$(jq -n --arg path "$script_file" --arg name "$(basename "$script_file")" \ '{path: $path, name: $name}') startup_scripts=$(echo "$startup_scripts" | jq ". + [$script_info]") fi done < <(find "$location" -maxdepth 1 -type f 2>/dev/null | head -20) fi done done # Background processes (excluding kernel threads) local background_processes='[]' while IFS= read -r line; do if [[ -n "$line" ]]; then local process_data=$(echo "$line" | jq -R 'split(" ") | {pid: .[0], user: .[1], cpu: .[2], mem: .[3], command: (.[4:] | join(" "))}') background_processes=$(echo "$background_processes" | jq ". + [$process_data]") fi done < <(ps aux --no-headers | grep -v "^\[" | head -30) system_services=$(echo "$system_services" | jq --argjson systemd_services "$systemd_services" \ --argjson cron_jobs "$cron_jobs" \ --argjson startup_scripts "$startup_scripts" \ --argjson background_processes "$background_processes" \ '.systemd_services = $systemd_services | .cron_jobs = $cron_jobs | .startup_scripts = $startup_scripts | .background_processes = $background_processes') jq --argjson system_services "$system_services" '.system_services = $system_services' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_web_services() { echo "🌐 Collecting web services..." local web_services=$(cat << 'EOF' { "web_servers": [], "reverse_proxies": [], "ssl_certificates": [], "web_applications": [] } EOF ) # Detect web servers local web_servers='[]' # Check for Nginx if command -v nginx &>/dev/null; then local nginx_version=$(nginx -v 2>&1 | cut -d: -f2 | tr -d ' ') local nginx_config=$(nginx -T 2>/dev/null | head -20 | jq -R . | jq -s 'join("\n")') local nginx_info=$(jq -n --arg version "$nginx_version" --argjson config "$nginx_config" \ '{name: "nginx", version: $version, config_sample: $config}') web_servers=$(echo "$web_servers" | jq ". + [$nginx_info]") fi # Check for Apache if command -v apache2 &>/dev/null || command -v httpd &>/dev/null; then local apache_cmd="apache2" command -v httpd &>/dev/null && apache_cmd="httpd" local apache_version=$($apache_cmd -v 2>/dev/null | head -1 | cut -d: -f2 | tr -d ' ') local apache_info=$(jq -n --arg version "$apache_version" \ '{name: "apache", version: $version}') web_servers=$(echo "$web_servers" | jq ". + [$apache_info]") fi # Check for Traefik (in containers) local traefik_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i traefik || echo "") if [[ -n "$traefik_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local traefik_info=$(jq -n --arg container "$container" \ '{name: "traefik", type: "container", container_name: $container}') web_servers=$(echo "$web_servers" | jq ". + [$traefik_info]") fi done <<< "$traefik_containers" fi # Detect reverse proxies local reverse_proxies='[]' # This would be detected above in web servers, but we can add specific proxy detection # SSL certificates local ssl_certificates='[]' local cert_locations=("/etc/ssl/certs" "/etc/letsencrypt/live" "/opt/*/ssl" "/home/*/ssl") for location_pattern in "${cert_locations[@]}"; do for location in $location_pattern; do if [[ -d "$location" ]]; then while IFS= read -r cert_file; do if [[ -f "$cert_file" ]]; then local cert_info=$(openssl x509 -in "$cert_file" -text -noout 2>/dev/null | head -20 || echo "") local subject=$(echo "$cert_info" | grep "Subject:" | head -1 | cut -d: -f2-) local issuer=$(echo "$cert_info" | grep "Issuer:" | head -1 | cut -d: -f2-) local not_after=$(echo "$cert_info" | grep "Not After" | head -1 | cut -d: -f2-) if [[ -n "$subject" ]]; then local cert_data=$(jq -n --arg path "$cert_file" --arg subject "$subject" --arg issuer "$issuer" --arg expires "$not_after" \ '{path: $path, subject: $subject, issuer: $issuer, expires: $expires}') ssl_certificates=$(echo "$ssl_certificates" | jq ". + [$cert_data]") fi fi done < <(find "$location" -name "*.crt" -o -name "*.pem" -o -name "cert.pem" 2>/dev/null | head -10) fi done done # Web applications (detect common patterns) local web_applications='[]' # Look for common web app directories local webapp_locations=("/var/www" "/opt" "/home/*/www" "/srv") for location_pattern in "${webapp_locations[@]}"; do for location in $location_pattern; do if [[ -d "$location" ]]; then while IFS= read -r app_dir; do if [[ -d "$app_dir" ]]; then local app_name=$(basename "$app_dir") local app_type="unknown" # Detect application type if [[ -f "$app_dir/index.php" ]]; then app_type="php" elif [[ -f "$app_dir/package.json" ]]; then app_type="nodejs" elif [[ -f "$app_dir/requirements.txt" ]]; then app_type="python" elif [[ -f "$app_dir/index.html" ]]; then app_type="static" fi local app_info=$(jq -n --arg name "$app_name" --arg path "$app_dir" --arg type "$app_type" \ '{name: $name, path: $path, type: $type}') web_applications=$(echo "$web_applications" | jq ". + [$app_info]") fi done < <(find "$location" -maxdepth 2 -type d 2>/dev/null | head -10) fi done done web_services=$(echo "$web_services" | jq --argjson web_servers "$web_servers" \ --argjson reverse_proxies "$reverse_proxies" \ --argjson ssl_certificates "$ssl_certificates" \ --argjson web_applications "$web_applications" \ '.web_servers = $web_servers | .reverse_proxies = $reverse_proxies | .ssl_certificates = $ssl_certificates | .web_applications = $web_applications') jq --argjson web_services "$web_services" '.web_services = $web_services' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_databases() { echo "🗃️ Collecting database services..." local databases=$(cat << 'EOF' { "postgresql": [], "mysql": [], "redis": [], "influxdb": [], "sqlite": [], "other": [] } EOF ) # PostgreSQL local postgresql='[]' # Check for PostgreSQL containers local postgres_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(postgres|postgresql)" || echo "") if [[ -n "$postgres_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local pg_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, env_vars: .Config.Env, mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') postgresql=$(echo "$postgresql" | jq ". + [$pg_info]") fi done <<< "$postgres_containers" fi # Check for system PostgreSQL if command -v psql &>/dev/null; then local pg_version=$(psql --version 2>/dev/null | head -1 || echo "unknown") local pg_system_info=$(jq -n --arg version "$pg_version" --arg type "system" \ '{type: $type, version: $version}') postgresql=$(echo "$postgresql" | jq ". + [$pg_system_info]") fi # MySQL/MariaDB local mysql='[]' # Check for MySQL containers local mysql_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(mysql|mariadb)" || echo "") if [[ -n "$mysql_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local mysql_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, env_vars: .Config.Env, mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') mysql=$(echo "$mysql" | jq ". + [$mysql_info]") fi done <<< "$mysql_containers" fi # Check for system MySQL if command -v mysql &>/dev/null; then local mysql_version=$(mysql --version 2>/dev/null | head -1 || echo "unknown") local mysql_system_info=$(jq -n --arg version "$mysql_version" --arg type "system" \ '{type: $type, version: $version}') mysql=$(echo "$mysql" | jq ". + [$mysql_system_info]") fi # Redis local redis='[]' # Check for Redis containers local redis_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i redis || echo "") if [[ -n "$redis_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local redis_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') redis=$(echo "$redis" | jq ". + [$redis_info]") fi done <<< "$redis_containers" fi # Check for system Redis if command -v redis-server &>/dev/null; then local redis_version=$(redis-server --version 2>/dev/null | head -1 || echo "unknown") local redis_system_info=$(jq -n --arg version "$redis_version" --arg type "system" \ '{type: $type, version: $version}') redis=$(echo "$redis" | jq ". + [$redis_system_info]") fi # InfluxDB local influxdb='[]' # Check for InfluxDB containers local influx_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i influx || echo "") if [[ -n "$influx_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local influx_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') influxdb=$(echo "$influxdb" | jq ". + [$influx_info]") fi done <<< "$influx_containers" fi # SQLite databases local sqlite='[]' local sqlite_locations=("/var/lib" "/opt" "/home" "/data") for location in "${sqlite_locations[@]}"; do if [[ -d "$location" ]]; then while IFS= read -r sqlite_file; do if [[ -f "$sqlite_file" ]]; then local sqlite_size=$(du -h "$sqlite_file" 2>/dev/null | awk '{print $1}' || echo "unknown") local sqlite_info=$(jq -n --arg path "$sqlite_file" --arg size "$sqlite_size" \ '{path: $path, size: $size}') sqlite=$(echo "$sqlite" | jq ". + [$sqlite_info]") fi done < <(find "$location" -name "*.db" -o -name "*.sqlite" -o -name "*.sqlite3" 2>/dev/null | head -10) fi done # Other databases local other='[]' local other_db_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(mongo|cassandra|elasticsearch|neo4j)" || echo "") if [[ -n "$other_db_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local other_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image }' || echo '{}') other=$(echo "$other" | jq ". + [$other_info]") fi done <<< "$other_db_containers" fi databases=$(echo "$databases" | jq --argjson postgresql "$postgresql" \ --argjson mysql "$mysql" \ --argjson redis "$redis" \ --argjson influxdb "$influxdb" \ --argjson sqlite "$sqlite" \ --argjson other "$other" \ '.postgresql = $postgresql | .mysql = $mysql | .redis = $redis | .influxdb = $influxdb | .sqlite = $sqlite | .other = $other') jq --argjson databases "$databases" '.databases = $databases' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_media_services() { echo "📺 Collecting media services..." local media_services=$(cat << 'EOF' { "jellyfin": [], "plex": [], "immich": [], "nextcloud": [], "media_libraries": [], "other_media": [] } EOF ) # Jellyfin local jellyfin='[]' local jellyfin_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i jellyfin || echo "") if [[ -n "$jellyfin_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local jellyfin_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, ports: [.NetworkSettings.Ports | to_entries[] | {port: .key, bindings: .value}], mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') jellyfin=$(echo "$jellyfin" | jq ". + [$jellyfin_info]") fi done <<< "$jellyfin_containers" fi # Plex local plex='[]' local plex_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i plex || echo "") if [[ -n "$plex_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local plex_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, ports: [.NetworkSettings.Ports | to_entries[] | {port: .key, bindings: .value}], mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') plex=$(echo "$plex" | jq ". + [$plex_info]") fi done <<< "$plex_containers" fi # Immich local immich='[]' local immich_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i immich || echo "") if [[ -n "$immich_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local immich_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, ports: [.NetworkSettings.Ports | to_entries[] | {port: .key, bindings: .value}], mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') immich=$(echo "$immich" | jq ". + [$immich_info]") fi done <<< "$immich_containers" fi # Nextcloud local nextcloud='[]' local nextcloud_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i nextcloud || echo "") if [[ -n "$nextcloud_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local nextcloud_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, ports: [.NetworkSettings.Ports | to_entries[] | {port: .key, bindings: .value}], mounts: [.Mounts[] | {source: .Source, destination: .Destination, type: .Type}] }' || echo '{}') nextcloud=$(echo "$nextcloud" | jq ". + [$nextcloud_info]") fi done <<< "$nextcloud_containers" fi # Media libraries local media_libraries='[]' local media_locations=("/media" "/mnt" "/data" "/home/*/Media" "/opt/media") for location_pattern in "${media_locations[@]}"; do for location in $location_pattern; do if [[ -d "$location" ]]; then local media_size=$(du -sh "$location" 2>/dev/null | awk '{print $1}' || echo "unknown") local media_count=$(find "$location" -type f 2>/dev/null | wc -l || echo "0") local media_info=$(jq -n --arg path "$location" --arg size "$media_size" --arg files "$media_count" \ '{path: $path, size: $size, file_count: ($files | tonumber)}') media_libraries=$(echo "$media_libraries" | jq ". + [$media_info]") fi done done # Other media services local other_media='[]' local other_media_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(sonarr|radarr|bazarr|lidarr|prowlarr|transmission|deluge)" || echo "") if [[ -n "$other_media_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local other_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, service_type: (.Config.Image | split("/")[1] // .Config.Image | split(":")[0]) }' || echo '{}') other_media=$(echo "$other_media" | jq ". + [$other_info]") fi done <<< "$other_media_containers" fi media_services=$(echo "$media_services" | jq --argjson jellyfin "$jellyfin" \ --argjson plex "$plex" \ --argjson immich "$immich" \ --argjson nextcloud "$nextcloud" \ --argjson media_libraries "$media_libraries" \ --argjson other_media "$other_media" \ '.jellyfin = $jellyfin | .plex = $plex | .immich = $immich | .nextcloud = $nextcloud | .media_libraries = $media_libraries | .other_media = $other_media') jq --argjson media_services "$media_services" '.media_services = $media_services' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_monitoring_services() { echo "📊 Collecting monitoring services..." local monitoring_services=$(cat << 'EOF' { "prometheus": [], "grafana": [], "influxdb": [], "log_management": [], "uptime_monitoring": [], "other_monitoring": [] } EOF ) # Prometheus local prometheus='[]' local prometheus_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i prometheus || echo "") if [[ -n "$prometheus_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local prometheus_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, ports: [.NetworkSettings.Ports | to_entries[] | {port: .key, bindings: .value}] }' || echo '{}') prometheus=$(echo "$prometheus" | jq ". + [$prometheus_info]") fi done <<< "$prometheus_containers" fi # Grafana local grafana='[]' local grafana_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -i grafana || echo "") if [[ -n "$grafana_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local grafana_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, ports: [.NetworkSettings.Ports | to_entries[] | {port: .key, bindings: .value}] }' || echo '{}') grafana=$(echo "$grafana" | jq ". + [$grafana_info]") fi done <<< "$grafana_containers" fi # Log management local log_management='[]' local log_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(elastic|kibana|logstash|fluentd|loki)" || echo "") if [[ -n "$log_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local log_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, service_type: (.Config.Image | split("/")[1] // .Config.Image | split(":")[0]) }' || echo '{}') log_management=$(echo "$log_management" | jq ". + [$log_info]") fi done <<< "$log_containers" fi # Other monitoring local other_monitoring='[]' local other_containers=$(docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(portainer|watchtower|node-exporter|cadvisor)" || echo "") if [[ -n "$other_containers" ]]; then while IFS= read -r container; do if [[ -n "$container" ]]; then local other_info=$(docker inspect "$container" 2>/dev/null | jq '.[0] | { container_name: .Name, image: .Config.Image, service_type: (.Config.Image | split("/")[1] // .Config.Image | split(":")[0]) }' || echo '{}') other_monitoring=$(echo "$other_monitoring" | jq ". + [$other_info]") fi done <<< "$other_containers" fi monitoring_services=$(echo "$monitoring_services" | jq --argjson prometheus "$prometheus" \ --argjson grafana "$grafana" \ --argjson log_management "$log_management" \ --argjson other_monitoring "$other_monitoring" \ '.prometheus = $prometheus | .grafana = $grafana | .log_management = $log_management | .other_monitoring = $other_monitoring') jq --argjson monitoring_services "$monitoring_services" '.monitoring_services = $monitoring_services' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_configuration_files() { echo "📝 Collecting configuration files..." local configuration_files=$(cat << 'EOF' { "docker_compose_files": [], "env_files": [], "config_directories": [], "ssl_certificates": [], "backup_configurations": [] } EOF ) # Docker Compose files (more detailed than before) local docker_compose_files='[]' local compose_locations=("/opt" "/home" "/var/lib" "$(pwd)" "/docker" "/containers") for location in "${compose_locations[@]}"; do if [[ -d "$location" ]]; then while IFS= read -r compose_file; do if [[ -f "$compose_file" ]]; then local compose_services=$(grep -E "^ [a-zA-Z]" "$compose_file" | awk -F: '{print $1}' | tr -d ' ' | jq -R . | jq -s . 2>/dev/null || echo '[]') local compose_networks=$(grep -A 10 "^networks:" "$compose_file" | grep -E "^ [a-zA-Z]" | awk -F: '{print $1}' | tr -d ' ' | jq -R . | jq -s . 2>/dev/null || echo '[]') local compose_info=$(jq -n --arg path "$compose_file" \ --arg size "$(wc -l < "$compose_file" 2>/dev/null || echo 0)" \ --argjson services "$compose_services" \ --argjson networks "$compose_networks" \ '{path: $path, lines: ($size | tonumber), services: $services, networks: $networks}') docker_compose_files=$(echo "$docker_compose_files" | jq ". + [$compose_info]") fi done < <(find "$location" -name "docker-compose*.yml" -o -name "compose*.yml" 2>/dev/null | head -20) fi done # Environment files local env_files='[]' for location in "${compose_locations[@]}"; do if [[ -d "$location" ]]; then while IFS= read -r env_file; do if [[ -f "$env_file" ]]; then local env_vars_count=$(grep -c "=" "$env_file" 2>/dev/null || echo "0") local env_info=$(jq -n --arg path "$env_file" --arg vars "$env_vars_count" \ '{path: $path, variable_count: ($vars | tonumber)}') env_files=$(echo "$env_files" | jq ". + [$env_info]") fi done < <(find "$location" -name ".env*" -o -name "*.env" 2>/dev/null | head -20) fi done # Configuration directories local config_directories='[]' local config_locations=("/etc" "/opt/*/config" "/home/*/config" "/var/lib/*/config") for location_pattern in "${config_locations[@]}"; do for location in $location_pattern; do if [[ -d "$location" ]]; then local config_size=$(du -sh "$location" 2>/dev/null | awk '{print $1}' || echo "unknown") local config_files=$(find "$location" -type f 2>/dev/null | wc -l || echo "0") local config_info=$(jq -n --arg path "$location" --arg size "$config_size" --arg files "$config_files" \ '{path: $path, size: $size, file_count: ($files | tonumber)}') config_directories=$(echo "$config_directories" | jq ". + [$config_info]") fi done done configuration_files=$(echo "$configuration_files" | jq --argjson docker_compose_files "$docker_compose_files" \ --argjson env_files "$env_files" \ --argjson config_directories "$config_directories" \ '.docker_compose_files = $docker_compose_files | .env_files = $env_files | .config_directories = $config_directories') jq --argjson configuration_files "$configuration_files" '.configuration_files = $configuration_files' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } collect_custom_applications() { echo "🔧 Collecting custom applications..." local custom_applications=$(cat << 'EOF' { "custom_scripts": [], "python_applications": [], "nodejs_applications": [], "automation_tools": [], "development_tools": [] } EOF ) # Custom scripts local custom_scripts='[]' local script_locations=("/opt" "/home/*/scripts" "/usr/local/bin" "$(pwd)") for location in "${script_locations[@]}"; do if [[ -d "$location" ]]; then while IFS= read -r script_file; do if [[ -f "$script_file" && -x "$script_file" ]]; then local script_lines=$(wc -l < "$script_file" 2>/dev/null || echo "0") local script_type="unknown" # Determine script type if [[ "$script_file" == *.py ]]; then script_type="python" elif [[ "$script_file" == *.sh ]]; then script_type="bash" elif [[ "$script_file" == *.js ]]; then script_type="javascript" else local shebang=$(head -1 "$script_file" 2>/dev/null || echo "") if [[ "$shebang" =~ python ]]; then script_type="python" elif [[ "$shebang" =~ bash ]]; then script_type="bash" fi fi local script_info=$(jq -n --arg path "$script_file" --arg type "$script_type" --arg lines "$script_lines" \ '{path: $path, type: $type, lines: ($lines | tonumber)}') custom_scripts=$(echo "$custom_scripts" | jq ". + [$script_info]") fi done < <(find "$location" -type f -name "*.py" -o -name "*.sh" -o -name "*.js" 2>/dev/null | head -20) fi done # Python applications local python_applications='[]' local python_locations=("/opt" "/home" "/var/lib") for location in "${python_locations[@]}"; do if [[ -d "$location" ]]; then while IFS= read -r python_app; do if [[ -f "$python_app/requirements.txt" || -f "$python_app/setup.py" || -f "$python_app/pyproject.toml" ]]; then local app_name=$(basename "$python_app") local has_requirements=$(test -f "$python_app/requirements.txt" && echo "true" || echo "false") local has_venv=$(test -d "$python_app/venv" -o -d "$python_app/.venv" && echo "true" || echo "false") local app_info=$(jq -n --arg name "$app_name" --arg path "$python_app" --arg requirements "$has_requirements" --arg venv "$has_venv" \ '{name: $name, path: $path, has_requirements: ($requirements | test("true")), has_virtualenv: ($venv | test("true"))}') python_applications=$(echo "$python_applications" | jq ". + [$app_info]") fi done < <(find "$location" -type d -maxdepth 3 2>/dev/null) fi done # Node.js applications local nodejs_applications='[]' for location in "${python_locations[@]}"; do if [[ -d "$location" ]]; then while IFS= read -r nodejs_app; do if [[ -f "$nodejs_app/package.json" ]]; then local app_name=$(basename "$nodejs_app") local has_node_modules=$(test -d "$nodejs_app/node_modules" && echo "true" || echo "false") local app_info=$(jq -n --arg name "$app_name" --arg path "$nodejs_app" --arg modules "$has_node_modules" \ '{name: $name, path: $path, has_node_modules: ($modules | test("true"))}') nodejs_applications=$(echo "$nodejs_applications" | jq ". + [$app_info]") fi done < <(find "$location" -name "package.json" 2>/dev/null | head -10 | xargs dirname) fi done custom_applications=$(echo "$custom_applications" | jq --argjson custom_scripts "$custom_scripts" \ --argjson python_applications "$python_applications" \ --argjson nodejs_applications "$nodejs_applications" \ '.custom_scripts = $custom_scripts | .python_applications = $python_applications | .nodejs_applications = $nodejs_applications') jq --argjson custom_applications "$custom_applications" '.custom_applications = $custom_applications' "$REPORT_FILE" > "${REPORT_FILE}.tmp" && mv "${REPORT_FILE}.tmp" "$REPORT_FILE" } generate_summary() { echo "" echo "📋 SERVICE INVENTORY SUMMARY" echo "==========================" # Extract key counts for summary local containers_count=$(jq '.docker_services.containers | length' "$REPORT_FILE") local images_count=$(jq '.docker_services.images | length' "$REPORT_FILE") local compose_files_count=$(jq '.configuration_files.docker_compose_files | length' "$REPORT_FILE") local databases_count=$(jq '[.databases.postgresql, .databases.mysql, .databases.redis, .databases.influxdb] | add | length' "$REPORT_FILE") local web_servers_count=$(jq '.web_services.web_servers | length' "$REPORT_FILE") local media_services_count=$(jq '[.media_services.jellyfin, .media_services.plex, .media_services.immich, .media_services.nextcloud] | add | length' "$REPORT_FILE") echo "Docker Containers: $containers_count" echo "Docker Images: $images_count" echo "Compose Files: $compose_files_count" echo "Databases: $databases_count" echo "Web Servers: $web_servers_count" echo "Media Services: $media_services_count" echo "" echo "Full report: $REPORT_FILE" echo "Next: Run data_layout_mapper.sh" } # Execute main function main "$@"