Compare commits
2 Commits
585240b03f
...
38c3dc910e
| Author | SHA1 | Date | |
|---|---|---|---|
| 38c3dc910e | |||
| 657b7d9a2d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1 @@
|
||||
todo.txt
|
||||
medium1_notes.txt
|
||||
medium2_notes.txt
|
||||
251
README.md
251
README.md
@@ -1,44 +1,241 @@
|
||||
# Drive Atlas
|
||||
|
||||
A powerful server drive mapping tool that generates visual ASCII representations of server layouts and provides comprehensive drive information based on the server's hostname.
|
||||
A powerful server drive mapping tool that generates visual ASCII representations of server layouts and provides comprehensive drive information. Maps physical drive bays to logical Linux device names using PCI bus paths for reliable, persistent identification.
|
||||
|
||||
## Features
|
||||
|
||||
- 🖼️ Visual ASCII art maps for different server types (large1, medium1, medium2, micro1/2)
|
||||
- 💽 Detailed NVMe drive information
|
||||
- 🔄 SATA drive listing with size and mount points
|
||||
- 🔍 PCI Bus Device Function (BDF) details
|
||||
- 📝 Drive identification by unique device IDs
|
||||
- Visual ASCII art maps showing physical drive bay layouts
|
||||
- Persistent drive identification using PCI paths (not device letters)
|
||||
- SMART health status and temperature monitoring
|
||||
- Support for SATA, NVMe, and USB drives
|
||||
- Detailed drive information including model, size, and health status
|
||||
- Per-server configuration for accurate physical-to-logical mapping
|
||||
|
||||
## Quick Start
|
||||
|
||||
Execute remotely using either command:
|
||||
|
||||
Execute remotely using curl:
|
||||
```bash
|
||||
bash <(curl -s http://10.10.10.110:3000/JWS/driveAtlas/raw/branch/main/driveAtlas.sh)
|
||||
bash <(curl -s http://10.10.10.63:3000/LotusGuild/driveAtlas/raw/branch/main/driveAtlas.sh)
|
||||
```
|
||||
- Using `wget`:
|
||||
|
||||
Or using wget:
|
||||
```bash
|
||||
bash <(wget -qO- http://10.10.10.110:3000/JWS/driveAtlas/raw/branch/main/driveAtlas.sh)
|
||||
bash <(wget -qO- http://10.10.10.63:3000/LotusGuild/driveAtlas/raw/branch/main/driveAtlas.sh)
|
||||
```
|
||||
|
||||
## Requirements
|
||||
Linux environment with bash
|
||||
sudo privileges for NVMe operations
|
||||
curl or wget for remote execution
|
||||
## Server Types
|
||||
The script supports different server layouts:
|
||||
|
||||
Type Description
|
||||
large1 3x3 grid layout (9 drives)
|
||||
medium1 2x4 + 2 layout (10 drives)
|
||||
medium2 Single row layout (10 drives)
|
||||
micro1/2 Compact 2-drive layout
|
||||
- Linux environment with bash
|
||||
- `sudo` privileges for SMART operations
|
||||
- `smartctl` (from smartmontools package)
|
||||
- `lsblk` and `lspci` (typically pre-installed)
|
||||
- Optional: `nvme-cli` for NVMe drives
|
||||
|
||||
## Server Configurations
|
||||
|
||||
### Chassis Types
|
||||
|
||||
| Chassis Type | Description | Servers Using It |
|
||||
|-------------|-------------|------------------|
|
||||
| **10-Bay Hot-swap** | Sliger CX471225 4U 10x 3.5" NAS (with unused 2x 5.25" bays) | compute-storage-01, compute-storage-gpu-01, storage-01 |
|
||||
| **Large1 Grid** | Unique 3x5 grid layout (1/1 configuration) | large1 |
|
||||
| **Micro** | Compact 2-drive layout | micro1, monitor-02 |
|
||||
|
||||
### Server Details
|
||||
|
||||
#### compute-storage-01 (formerly medium2)
|
||||
- **Chassis:** Sliger CX471225 4U (10-Bay Hot-swap)
|
||||
- **Motherboard:** B650D4U3-2Q/BCM
|
||||
- **Controllers:**
|
||||
- 0c:00.0 - Front hot-swap bays
|
||||
- 0d:00.0 - M.2 NVMe slot
|
||||
- 0b:00.0 - USB controller
|
||||
- **Status:** Partially mapped (bays 3-6 only)
|
||||
|
||||
#### storage-01
|
||||
- **Chassis:** Sliger CX471225 4U (10-Bay Hot-swap)
|
||||
- **Motherboard:** Different from compute-storage-01
|
||||
- **Controllers:** Motherboard SATA only (no HBA currently)
|
||||
- **Status:** Requires PCI path mapping
|
||||
|
||||
#### large1
|
||||
- **Chassis:** Unique 3x5 grid (15 bays total)
|
||||
- **Note:** 1/1 configuration, will not be replicated
|
||||
- **Status:** Requires PCI path mapping
|
||||
|
||||
#### compute-storage-gpu-01
|
||||
- **Chassis:** Sliger CX471225 4U (10-Bay Hot-swap)
|
||||
- **Motherboard:** Same as compute-storage-01
|
||||
- **Status:** Requires PCI path mapping
|
||||
|
||||
## How It Works
|
||||
|
||||
### PCI Path-Based Mapping
|
||||
|
||||
Drive Atlas uses `/dev/disk/by-path/` to create persistent mappings between physical drive bays and Linux device names. This is superior to using device letters (sda, sdb, etc.) which can change between boots.
|
||||
|
||||
**Example PCI path:**
|
||||
```
|
||||
pci-0000:0c:00.0-ata-1 → /dev/sda
|
||||
```
|
||||
|
||||
This tells us:
|
||||
- `0000:0c:00.0` - PCI bus address of the storage controller
|
||||
- `ata-1` - Port 1 on that controller
|
||||
- Maps to physical bay 3 on compute-storage-01
|
||||
|
||||
### Configuration
|
||||
|
||||
Server mappings are defined in the `SERVER_MAPPINGS` associative array in [driveAtlas.sh](driveAtlas.sh):
|
||||
|
||||
```bash
|
||||
declare -A SERVER_MAPPINGS=(
|
||||
["compute-storage-01"]="
|
||||
pci-0000:0c:00.0-ata-1 3
|
||||
pci-0000:0c:00.0-ata-2 4
|
||||
pci-0000:0d:00.0-nvme-1 m2-1
|
||||
"
|
||||
)
|
||||
```
|
||||
|
||||
## Setting Up a New Server
|
||||
|
||||
### Step 1: Run Diagnostic Script
|
||||
|
||||
First, gather PCI path information:
|
||||
|
||||
```bash
|
||||
bash diagnose-drives.sh > server-diagnostic.txt
|
||||
```
|
||||
|
||||
This will show all available PCI paths and their associated drives.
|
||||
|
||||
### Step 2: Physical Bay Identification
|
||||
|
||||
For each populated drive bay:
|
||||
|
||||
1. Note the physical bay number (labeled on chassis)
|
||||
2. Identify a unique characteristic (size, model, or serial number)
|
||||
3. Match it to the PCI path from the diagnostic output
|
||||
|
||||
**Pro tip:** If uncertain, remove one drive at a time and re-run the diagnostic to see which PCI path disappears.
|
||||
|
||||
### Step 3: Create Mapping
|
||||
|
||||
Add a new entry to `SERVER_MAPPINGS` in [driveAtlas.sh](driveAtlas.sh):
|
||||
|
||||
```bash
|
||||
["your-hostname"]="
|
||||
pci-0000:XX:XX.X-ata-1 1
|
||||
pci-0000:XX:XX.X-ata-2 2
|
||||
# ... etc
|
||||
"
|
||||
```
|
||||
|
||||
Also add the chassis type to `CHASSIS_TYPES`:
|
||||
|
||||
```bash
|
||||
["your-hostname"]="10bay"
|
||||
```
|
||||
|
||||
### Step 4: Test
|
||||
|
||||
Run the main script and verify the layout matches your physical configuration:
|
||||
|
||||
```bash
|
||||
bash driveAtlas.sh
|
||||
```
|
||||
|
||||
Use debug mode to see the mappings:
|
||||
|
||||
```bash
|
||||
DEBUG=1 bash driveAtlas.sh
|
||||
```
|
||||
|
||||
## Output Example
|
||||
The script provides:
|
||||
|
||||
ASCII visualization of server layout
|
||||
NVMe drive listing
|
||||
SATA drive information
|
||||
PCI BDF details
|
||||
Drive ID mappings
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ compute-storage-01 │
|
||||
│ 10-Bay Hot-swap Chassis │
|
||||
│ │
|
||||
│ M.2 NVMe Slot │
|
||||
│ ┌──────────┐ │
|
||||
│ │ nvme0n1 │ │
|
||||
│ └──────────┘ │
|
||||
│ │
|
||||
│ Front Hot-swap Bays │
|
||||
│ ┌──────────┐┌──────────┐┌──────────┐┌──────────┐... │
|
||||
│ │1: EMPTY ││2: EMPTY ││3: sda ││4: sdb │... │
|
||||
│ └──────────┘└──────────┘└──────────┘└──────────┘... │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
|
||||
=== Drive Details with SMART Status ===
|
||||
DEVICE SIZE TYPE TEMP HEALTH MODEL
|
||||
--------------------------------------------------------------------------------
|
||||
/dev/sda 2TB HDD 35°C ✓ WD20EFRX-68EUZN0
|
||||
/dev/nvme0n1 1TB SSD 42°C ✓ Samsung 980 PRO
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Drive shows as EMPTY but is physically present
|
||||
|
||||
- Check if the drive is detected: `ls -la /dev/disk/by-path/`
|
||||
- Verify the PCI path in the mapping matches the actual path
|
||||
- Ensure the drive has power and SATA/power connections are secure
|
||||
|
||||
### PCI paths don't match between servers with "identical" hardware
|
||||
|
||||
- Even identical motherboards can have different PCI addressing
|
||||
- BIOS settings can affect PCI enumeration
|
||||
- HBA installation in different PCIe slots changes addresses
|
||||
- Cable routing to different SATA ports changes the ata-N number
|
||||
|
||||
### SMART data not showing
|
||||
|
||||
- Ensure `smartmontools` is installed: `sudo apt install smartmontools`
|
||||
- Some drives don't report temperature
|
||||
- USB-connected drives may not support SMART
|
||||
- Run `sudo smartctl -i /dev/sdX` manually to check
|
||||
|
||||
## Files
|
||||
|
||||
- [driveAtlas.sh](driveAtlas.sh) - Main script
|
||||
- [diagnose-drives.sh](diagnose-drives.sh) - PCI path diagnostic tool
|
||||
- [README.md](README.md) - This file
|
||||
- [todo.txt](todo.txt) - Development notes
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding support for a new server:
|
||||
|
||||
1. Run `diagnose-drives.sh` and save output
|
||||
2. Physically label or identify drives
|
||||
3. Create mapping in `SERVER_MAPPINGS`
|
||||
4. Test thoroughly
|
||||
5. Document any unique hardware configurations
|
||||
6. Update this README
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Why PCI Paths?
|
||||
|
||||
Linux device names (sda, sdb, etc.) are assigned in discovery order, which can change:
|
||||
- Between kernel versions
|
||||
- After BIOS updates
|
||||
- When drives are added/removed
|
||||
- Due to timing variations at boot
|
||||
|
||||
PCI paths are deterministic and based on physical hardware topology.
|
||||
|
||||
### Bay Numbering Conventions
|
||||
|
||||
- **10-bay chassis:** Bays numbered 1-10 (left to right, top to bottom)
|
||||
- **M.2 slots:** Labeled as `m2-1`, `m2-2`, etc.
|
||||
- **USB drives:** Labeled as `usb1`, `usb2`, etc.
|
||||
- **Large1:** Grid numbering 1-9 (3x3 displayed, additional bays documented in mapping)
|
||||
|
||||
## License
|
||||
|
||||
Internal tool for LotusGuild infrastructure.
|
||||
|
||||
59
diagnose-drives.sh
Normal file
59
diagnose-drives.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Drive Atlas Diagnostic Script
|
||||
# Run this on each server to gather PCI path information
|
||||
|
||||
echo "=== Server Information ==="
|
||||
echo "Hostname: $(hostname)"
|
||||
echo "Date: $(date)"
|
||||
echo ""
|
||||
|
||||
echo "=== All /dev/disk/by-path/ entries ==="
|
||||
ls -la /dev/disk/by-path/ | grep -v "part" | sort
|
||||
echo ""
|
||||
|
||||
echo "=== Organized by PCI Address ==="
|
||||
for path in /dev/disk/by-path/*; do
|
||||
if [ -L "$path" ]; then
|
||||
# Skip partitions
|
||||
if [[ "$path" =~ -part[0-9]+$ ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
basename_path=$(basename "$path")
|
||||
target=$(readlink -f "$path")
|
||||
device=$(basename "$target")
|
||||
|
||||
echo "Path: $basename_path"
|
||||
echo " -> Device: $device"
|
||||
|
||||
# Try to get size
|
||||
if [ -b "$target" ]; then
|
||||
size=$(lsblk -d -n -o SIZE "$target" 2>/dev/null)
|
||||
echo " -> Size: $size"
|
||||
fi
|
||||
|
||||
# Try to get SMART info for model
|
||||
if command -v smartctl >/dev/null 2>&1; then
|
||||
model=$(sudo smartctl -i "$target" 2>/dev/null | grep "Device Model\|Model Number" | cut -d: -f2 | xargs)
|
||||
if [ -n "$model" ]; then
|
||||
echo " -> Model: $model"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== PCI Devices with Storage Controllers ==="
|
||||
lspci | grep -i "storage\|raid\|sata\|sas\|nvme"
|
||||
echo ""
|
||||
|
||||
echo "=== Current Block Devices ==="
|
||||
lsblk -d -o NAME,SIZE,TYPE,TRAN | grep -v "rbd\|loop"
|
||||
echo ""
|
||||
|
||||
echo "=== Recommendations ==="
|
||||
echo "1. Note the PCI addresses (e.g., 0c:00.0) of your storage controllers"
|
||||
echo "2. For each bay, physically identify which drive is in it"
|
||||
echo "3. Match the PCI path pattern to the bay number"
|
||||
echo "4. Example: pci-0000:0c:00.0-ata-1 might be bay 1 on controller 0c:00.0"
|
||||
443
driveAtlas.sh
443
driveAtlas.sh
@@ -1,84 +1,85 @@
|
||||
#!/bin/bash
|
||||
|
||||
get_device_info() {
|
||||
local pci_addr=$1
|
||||
local info=$(lspci -s "$pci_addr")
|
||||
echo "$info"
|
||||
}
|
||||
#==============================================================================
|
||||
# Drive Atlas - Server Drive Mapping Tool
|
||||
# Maps physical drive bays to logical device names using PCI paths
|
||||
#==============================================================================
|
||||
|
||||
get_drive_details() {
|
||||
local device=$1
|
||||
local size=$(lsblk -d -o NAME,SIZE | grep "$device" | awk '{print $2}')
|
||||
echo "$size"
|
||||
}
|
||||
#------------------------------------------------------------------------------
|
||||
# Chassis Type Definitions
|
||||
# These define the physical layout and display formatting for each chassis type
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
get_drive_smart_info() {
|
||||
local device=$1
|
||||
local smart_info=$(sudo smartctl -A -i -H /dev/$device 2>/dev/null)
|
||||
local temp=$(echo "$smart_info" | grep "Temperature" | awk '{print $10}' | head -1)
|
||||
local type=$(echo "$smart_info" | grep "Rotation Rate" | grep -q "Solid State" && echo "SSD" || echo "HDD")
|
||||
local health=$(echo "$smart_info" | grep "SMART overall-health" | grep -q "PASSED" && echo "✓" || echo "✗")
|
||||
local model=$(echo "$smart_info" | grep "Device Model" | cut -d: -f2 | xargs)
|
||||
generate_10bay_layout() {
|
||||
local hostname=$1
|
||||
build_drive_map
|
||||
|
||||
echo "$type|$temp°C|$health|$model"
|
||||
}
|
||||
|
||||
get_drives_info() {
|
||||
local path="/dev/disk/by-path"
|
||||
for drive in "$path"/*; do
|
||||
if [ -L "$drive" ]; then
|
||||
echo "$(basename "$drive") $(readlink -f "$drive")"
|
||||
fi
|
||||
# Calculate max width needed for drive names
|
||||
max_width=0
|
||||
for bay in {1..10} "m2-1" "usb1" "usb2"; do
|
||||
drive_text="${DRIVE_MAP[$bay]:-EMPTY}"
|
||||
text_len=$((${#bay} + 1 + ${#drive_text}))
|
||||
[[ $text_len -gt $max_width ]] && max_width=$text_len
|
||||
done
|
||||
}
|
||||
|
||||
declare -A DRIVE_MAPPINGS=(
|
||||
["medium2"]="
|
||||
pci-0000:0c:00.0-ata-3 5
|
||||
pci-0000:0c:00.0-ata-4 6
|
||||
pci-0000:0c:00.0-ata-1 3
|
||||
pci-0000:0c:00.0-ata-2 4
|
||||
pci-0000:0d:00.0-nvme-1 11
|
||||
pci-0000:0b:00.0-usb-0:3:1.0-scsi-0:0:0:0 usb1
|
||||
pci-0000:0b:00.0-usb-0:4:1.0-scsi-0:0:0:0 usb2
|
||||
"
|
||||
)
|
||||
# Add padding for box borders
|
||||
box_width=$((max_width + 4))
|
||||
|
||||
build_drive_map() {
|
||||
local host=$(hostname)
|
||||
declare -A drive_map
|
||||
# Create box drawing elements
|
||||
h_line=$(printf '%*s' "$box_width" '' | tr ' ' '─')
|
||||
|
||||
echo "DEBUG: Current host: $host"
|
||||
echo "DEBUG: Mapping found: ${DRIVE_MAPPINGS[$host]}"
|
||||
|
||||
local mapping=${DRIVE_MAPPINGS[$host]}
|
||||
|
||||
if [[ -n "$mapping" ]]; then
|
||||
while read -r path slot; do
|
||||
[[ -z "$path" || -z "$slot" ]] && continue
|
||||
|
||||
echo "DEBUG: Checking path: $path for slot: $slot"
|
||||
|
||||
if [[ -L "/dev/disk/by-path/$path" ]]; then
|
||||
local drive=$(readlink -f "/dev/disk/by-path/$path" | sed 's/.*\///')
|
||||
drive_map[$slot]=$drive
|
||||
echo "DEBUG: Mapped slot $slot to drive $drive"
|
||||
fi
|
||||
done <<< "$mapping"
|
||||
# USB Section (if applicable)
|
||||
if [[ -n "${DRIVE_MAP[usb1]}" || -n "${DRIVE_MAP[usb2]}" ]]; then
|
||||
printf "\n External USB\n"
|
||||
printf " ┌%s┐ ┌%s┐\n" "$h_line" "$h_line"
|
||||
printf " │ %-${max_width}s │ │ %-${max_width}s │\n" "${DRIVE_MAP[usb1]:-EMPTY}" "${DRIVE_MAP[usb2]:-EMPTY}"
|
||||
printf " └%s┘ └%s┘\n\n" "$h_line" "$h_line"
|
||||
fi
|
||||
|
||||
# Make drive_map available globally
|
||||
declare -g -A DRIVE_MAP=()
|
||||
for key in "${!drive_map[@]}"; do
|
||||
DRIVE_MAP[$key]=${drive_map[$key]}
|
||||
echo "DEBUG: Final mapping - slot $key: ${drive_map[$key]}"
|
||||
# Main chassis section
|
||||
printf "┌──────────────────────────────────────────────────────────────┐\n"
|
||||
printf "│ %-58s │\n" "$hostname"
|
||||
printf "│ %-58s │\n" "10-Bay Hot-swap Chassis"
|
||||
printf "│ │\n"
|
||||
|
||||
# M.2 NVMe slot if present
|
||||
if [[ -n "${DRIVE_MAP[m2-1]}" ]]; then
|
||||
printf "│ M.2 NVMe Slot │\n"
|
||||
printf "│ ┌%s┐ │\n" "$h_line"
|
||||
printf "│ │ %-${max_width}s │ │\n" "${DRIVE_MAP[m2-1]:-EMPTY}"
|
||||
printf "│ └%s┘ │\n" "$h_line"
|
||||
printf "│ │\n"
|
||||
fi
|
||||
|
||||
printf "│ Front Hot-swap Bays │\n"
|
||||
|
||||
# Create bay rows
|
||||
printf "│ "
|
||||
for bay in {1..10}; do
|
||||
printf "┌%s┐" "$h_line"
|
||||
done
|
||||
printf " │\n│ "
|
||||
|
||||
for bay in {1..10}; do
|
||||
printf "│%-2d:%-${max_width}s │" "$bay" "${DRIVE_MAP[$bay]:-EMPTY}"
|
||||
done
|
||||
printf " │\n│ "
|
||||
|
||||
for bay in {1..10}; do
|
||||
printf "└%s┘" "$h_line"
|
||||
done
|
||||
printf " │\n"
|
||||
|
||||
printf "└──────────────────────────────────────────────────────────────┘\n"
|
||||
}
|
||||
|
||||
# Define the ASCII art maps
|
||||
large1='''
|
||||
generate_large1_layout() {
|
||||
build_drive_map
|
||||
|
||||
cat << 'EOF'
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ large1 │
|
||||
│ Unique 3x5 Grid Chassis │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ Motherboard │ │
|
||||
@@ -104,214 +105,164 @@ large1='''
|
||||
│ │ │ │ │ │ │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
'''
|
||||
|
||||
compute-storage-gpu-01='''
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │
|
||||
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||
│ │ 5 │ │ 6 │ │ 7 │ │ 8 │ │
|
||||
│ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ ┌─────────┐ │
|
||||
│ compute-storage-gpu-01 │ 9 │ │
|
||||
│ └─────────┘ │
|
||||
│ ┌─────────┐ │
|
||||
│ │ 10 │ │
|
||||
│ └─────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
'''
|
||||
|
||||
generate_medium2_layout() {
|
||||
build_drive_map
|
||||
|
||||
# Calculate max width needed for drive names
|
||||
max_width=0
|
||||
for bay in {1..10} "11" "usb1" "usb2"; do
|
||||
drive_text="${DRIVE_MAP[$bay]:-EMPTY}"
|
||||
text_len=$((${#bay} + 1 + ${#drive_text}))
|
||||
[[ $text_len -gt $max_width ]] && max_width=$text_len
|
||||
done
|
||||
|
||||
# Add padding for box borders
|
||||
box_width=$((max_width + 4))
|
||||
|
||||
# Create box drawing elements
|
||||
h_line=$(printf '%*s' "$box_width" '' | tr ' ' '─')
|
||||
|
||||
# USB Section
|
||||
printf "\n External USB [0b:00.0]\n"
|
||||
printf " ┌%s┐ ┌%s┐\n" "$h_line" "$h_line"
|
||||
printf " │ %-${max_width}s │ │ %-${max_width}s │\n" "${DRIVE_MAP[usb1]:-EMPTY}" "${DRIVE_MAP[usb2]:-EMPTY}"
|
||||
printf " └%s┘ └%s┘\n\n" "$h_line" "$h_line"
|
||||
|
||||
# Main chassis section
|
||||
printf "┌──────────────────────────────────────────────────────────────┐\n"
|
||||
printf "│ B650D4U3-2Q/BCM │\n"
|
||||
printf "│ │\n"
|
||||
printf "│ NVMe [0d:00.0] Bay 11 │\n"
|
||||
printf "│ ┌%s┐ │\n" "$h_line"
|
||||
printf "│ │ %-${max_width}s │ │\n" "${DRIVE_MAP[11]:-EMPTY}"
|
||||
printf "│ └%s┘ │\n" "$h_line"
|
||||
printf "│ │\n"
|
||||
printf "│ Front Hot-swap Bays [0c:00.0] │\n"
|
||||
|
||||
# Create bay rows
|
||||
printf "│ "
|
||||
for bay in {1..10}; do
|
||||
printf "┌%s┐" "$h_line"
|
||||
done
|
||||
printf " │\n│ "
|
||||
|
||||
for bay in {1..10}; do
|
||||
printf "│%-2d:%-${max_width}s │" "$bay" "${DRIVE_MAP[$bay]:-EMPTY}"
|
||||
done
|
||||
printf " │\n│ "
|
||||
|
||||
for bay in {1..10}; do
|
||||
printf "└%s┘" "$h_line"
|
||||
done
|
||||
printf " │\n"
|
||||
|
||||
printf "└──────────────────────────────────────────────────────────────┘\n"
|
||||
EOF
|
||||
}
|
||||
|
||||
microGeneric='''
|
||||
┌─┐ ┌─┐
|
||||
┌└─┘──└─┘┐
|
||||
│ 1 2 │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└────────┘
|
||||
'''
|
||||
#------------------------------------------------------------------------------
|
||||
# Server-Specific Drive Mappings
|
||||
# Maps PCI paths to physical bay numbers for each server
|
||||
# Format: "pci-path bay-number"
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
declare -A SERVER_MAPPINGS=(
|
||||
# compute-storage-01 (formerly medium2)
|
||||
# Motherboard: B650D4U3-2Q/BCM
|
||||
# Controller at 0c:00.0 for hot-swap bays
|
||||
# Controller at 0d:00.0 for M.2 NVMe
|
||||
["compute-storage-01"]="
|
||||
pci-0000:0c:00.0-ata-3 5
|
||||
pci-0000:0c:00.0-ata-4 6
|
||||
pci-0000:0c:00.0-ata-1 3
|
||||
pci-0000:0c:00.0-ata-2 4
|
||||
pci-0000:0d:00.0-nvme-1 m2-1
|
||||
pci-0000:0b:00.0-usb-0:3:1.0-scsi-0:0:0:0 usb1
|
||||
pci-0000:0b:00.0-usb-0:4:1.0-scsi-0:0:0:0 usb2
|
||||
"
|
||||
|
||||
# storage-01
|
||||
# Different motherboard, no HBA currently
|
||||
# TODO: Map actual PCI paths after running diagnose-drives.sh
|
||||
["storage-01"]="
|
||||
"
|
||||
|
||||
# large1
|
||||
# Unique chassis - 1/1 configuration
|
||||
# TODO: Map actual PCI paths after running diagnose-drives.sh
|
||||
["large1"]="
|
||||
"
|
||||
)
|
||||
|
||||
declare -A CHASSIS_TYPES=(
|
||||
["compute-storage-01"]="10bay"
|
||||
["compute-storage-gpu-01"]="10bay"
|
||||
["storage-01"]="10bay"
|
||||
["large1"]="large1"
|
||||
["micro1"]="micro"
|
||||
["monitor-02"]="micro"
|
||||
)
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Core Functions
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
build_drive_map() {
|
||||
local host=$(hostname)
|
||||
declare -A drive_map
|
||||
|
||||
local mapping=${SERVER_MAPPINGS[$host]}
|
||||
|
||||
if [[ -n "$mapping" ]]; then
|
||||
while read -r path slot; do
|
||||
[[ -z "$path" || -z "$slot" ]] && continue
|
||||
|
||||
if [[ -L "/dev/disk/by-path/$path" ]]; then
|
||||
local drive=$(readlink -f "/dev/disk/by-path/$path" | sed 's/.*\///')
|
||||
drive_map[$slot]=$drive
|
||||
fi
|
||||
done <<< "$mapping"
|
||||
fi
|
||||
|
||||
# Make drive_map available globally
|
||||
declare -g -A DRIVE_MAP=()
|
||||
for key in "${!drive_map[@]}"; do
|
||||
DRIVE_MAP[$key]=${drive_map[$key]}
|
||||
done
|
||||
}
|
||||
|
||||
get_drive_smart_info() {
|
||||
local device=$1
|
||||
local smart_info=$(sudo smartctl -A -i -H /dev/$device 2>/dev/null)
|
||||
local temp=$(echo "$smart_info" | grep "Temperature" | awk '{print $10}' | head -1)
|
||||
local type=$(echo "$smart_info" | grep "Rotation Rate" | grep -q "Solid State" && echo "SSD" || echo "HDD")
|
||||
local health=$(echo "$smart_info" | grep "SMART overall-health" | grep -q "PASSED" && echo "✓" || echo "✗")
|
||||
local model=$(echo "$smart_info" | grep "Device Model\|Model Number" | cut -d: -f2 | xargs)
|
||||
|
||||
echo "$type|$temp°C|$health|$model"
|
||||
}
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Main Display Logic
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Get the hostname
|
||||
HOSTNAME=$(hostname)
|
||||
CHASSIS_TYPE=${CHASSIS_TYPES[$HOSTNAME]:-"unknown"}
|
||||
|
||||
# ASCII art based on hostname
|
||||
case "$HOSTNAME" in
|
||||
# Display chassis layout
|
||||
case "$CHASSIS_TYPE" in
|
||||
"10bay")
|
||||
generate_10bay_layout "$HOSTNAME"
|
||||
;;
|
||||
"large1")
|
||||
echo -e "$large1"
|
||||
generate_large1_layout
|
||||
;;
|
||||
"compute-storage-gpu-01")
|
||||
echo -e "$compute-storage-gpu-01"
|
||||
;;
|
||||
"medium2")
|
||||
generate_medium2_layout
|
||||
;;
|
||||
"micro1" | "monitor-02")
|
||||
echo -e "$microGeneric"
|
||||
"micro")
|
||||
echo "Micro server layout not yet implemented"
|
||||
;;
|
||||
*)
|
||||
echo -e "No ASCII map defined for this hostname."
|
||||
echo "┌─────────────────────────────────────────────────────────┐"
|
||||
echo "│ Unknown server: $HOSTNAME"
|
||||
echo "│ No chassis mapping defined yet"
|
||||
echo "│ Run diagnose-drives.sh to gather PCI path information"
|
||||
echo "└─────────────────────────────────────────────────────────┘"
|
||||
;;
|
||||
esac
|
||||
|
||||
map_drives_to_layout() {
|
||||
local server_type=$1
|
||||
case $server_type in
|
||||
"large1")
|
||||
for i in {1..9}; do
|
||||
local drive_info=$(get_drive_info_for_position $i)
|
||||
echo "Position $i: $drive_info"
|
||||
done
|
||||
;;
|
||||
esac
|
||||
}
|
||||
#------------------------------------------------------------------------------
|
||||
# Drive Details Section
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
DRIVE_PATHS=$(get_drives_info | awk '{print $1, $2}')
|
||||
|
||||
# Initialize array for "not found" messages
|
||||
not_found=()
|
||||
|
||||
|
||||
echo -e "\n=== Drive Details with SMART Status ===\n"
|
||||
printf "%-15s %-10s %-8s %-8s %-20s %-30s\n" "DEVICE" "SIZE" "TYPE" "TEMP" "HEALTH" "MODEL"
|
||||
echo -e "\n=== Drive Details with SMART Status ==="
|
||||
printf "%-15s %-10s %-8s %-8s %-8s %-30s\n" "DEVICE" "SIZE" "TYPE" "TEMP" "HEALTH" "MODEL"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
# For SATA drives
|
||||
lsblk -d -o NAME | grep -v "nvme" | grep -v "rbd" | while read device; do
|
||||
size=$(get_drive_details "$device")
|
||||
# SATA/SAS drives
|
||||
lsblk -d -o NAME | grep -v "nvme" | grep -v "rbd" | grep -v "loop" | grep -v "NAME" | while read device; do
|
||||
if [ -b "/dev/$device" ]; then
|
||||
size=$(lsblk -d -n -o SIZE "/dev/$device" 2>/dev/null)
|
||||
smart_info=$(get_drive_smart_info "$device")
|
||||
IFS='|' read -r type temp health model <<< "$smart_info"
|
||||
printf "%-15s %-10s %-8s %-8s %-20s %-30s\n" "/dev/$device" "$size" "$type" "$temp" "$health" "$model"
|
||||
printf "%-15s %-10s %-8s %-8s %-8s %-30s\n" "/dev/$device" "$size" "$type" "$temp" "$health" "$model"
|
||||
fi
|
||||
done
|
||||
|
||||
# For NVMe drives
|
||||
# NVMe drives
|
||||
if command -v nvme >/dev/null 2>&1; then
|
||||
nvme_drives=$(sudo nvme list 2>/dev/null | grep "^/dev")
|
||||
if [ -n "$nvme_drives" ]; then
|
||||
while read -r line; do
|
||||
device=$(echo "$line" | awk '{print $1}' | sed 's/.*\///')
|
||||
size=$(echo "$line" | awk '{print $6}')
|
||||
smart_info=$(get_drive_smart_info "$device")
|
||||
IFS='|' read -r type temp health model <<< "$smart_info"
|
||||
printf "%-15s %-10s %-8s %-8s %-20s %-30s\n" "/dev/$device" "$size" "$type" "$temp" "$health" "$model"
|
||||
done <<< "$nvme_drives"
|
||||
echo -e "\n=== NVMe Drives ==="
|
||||
printf "%-15s %-10s %-10s %-40s\n" "DEVICE" "SIZE" "TYPE" "MODEL"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
echo "$nvme_drives" | awk '{printf "%-15s %-10s %-10s %-40s\n", $1, $6, "NVMe", $3}'
|
||||
fi
|
||||
fi
|
||||
|
||||
# Show NVMe Drives only if present
|
||||
nvme_drives=$(sudo nvme list | grep "^/dev")
|
||||
if [ -n "$nvme_drives" ]; then
|
||||
echo -e "\n=== NVMe Drives ===\n"
|
||||
printf "%-15s %-10s %-10s %-20s\n" "DEVICE" "SIZE" "TYPE" "MODEL"
|
||||
echo "------------------------------------------------------------"
|
||||
echo "$nvme_drives" | awk '{printf "%-15s %-10s %-10s %-20s\n", $1, $6, "NVMe", $3}'
|
||||
else
|
||||
not_found+=("NVMe drives")
|
||||
fi
|
||||
#------------------------------------------------------------------------------
|
||||
# Optional sections
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
# Show MMC Drives only if present
|
||||
mmc_output=$(lsblk -o NAME,SIZE,TYPE,MOUNTPOINT | grep "mmcblk" | sort)
|
||||
if [ -n "$mmc_output" ]; then
|
||||
echo -e "\n=== MMC Drives ===\n"
|
||||
printf "%-15s %-10s %-10s %-20s\n" "DEVICE" "SIZE" "TYPE" "MOUNTPOINT"
|
||||
echo "------------------------------------------------------------"
|
||||
echo "$mmc_output"
|
||||
fi
|
||||
|
||||
# Show SATA Drives only if present
|
||||
sata_output=$(lsblk -d -o NAME,SIZE,TYPE,MOUNTPOINT | grep "disk" | grep -v "nvme" | grep -v "rbd" | sort | column -t)s
|
||||
if [ -n "$sata_output" ]; then
|
||||
echo -e "\n=== SATA Drives ===\n"
|
||||
printf "%-15s %-10s %-10s %-20s\n" "DEVICE" "SIZE" "TYPE" "MOUNTPOINT"
|
||||
echo "------------------------------------------------------------"
|
||||
echo "$sata_output"
|
||||
fi
|
||||
|
||||
# Show Ceph RBD Devices only if present
|
||||
rbd_output=$(lsblk -o NAME,SIZE,TYPE,MOUNTPOINT | grep "rbd" | sort -V)
|
||||
# Ceph RBD Devices
|
||||
rbd_output=$(lsblk -o NAME,SIZE,TYPE,MOUNTPOINT 2>/dev/null | grep "rbd" | sort -V)
|
||||
if [ -n "$rbd_output" ]; then
|
||||
echo -e "\n=== Ceph RBD Devices ===\n"
|
||||
echo -e "\n=== Ceph RBD Devices ==="
|
||||
printf "%-15s %-10s %-10s %-20s\n" "DEVICE" "SIZE" "TYPE" "MOUNTPOINT"
|
||||
echo "------------------------------------------------------------"
|
||||
echo "$rbd_output"
|
||||
else
|
||||
not_found+=("RBD devices")
|
||||
fi
|
||||
|
||||
# Check RAID
|
||||
if ! [ -f /proc/mdstat ] || ! grep -q "active" /proc/mdstat; then
|
||||
not_found+=("Software RAID")
|
||||
fi
|
||||
|
||||
# Check ZFS
|
||||
if ! command -v zpool >/dev/null 2>&1 || [ -z "$(sudo zpool status 2>/dev/null)" ]; then
|
||||
not_found+=("ZFS pools")
|
||||
fi
|
||||
|
||||
# Display consolidated "not found" messages at the end
|
||||
if [ ${#not_found[@]} -gt 0 ]; then
|
||||
echo -e "\n=== Not Found ===\n"
|
||||
printf "%s\n" "${not_found[@]}"
|
||||
# Show mapping diagnostic info if DEBUG is set
|
||||
if [[ -n "$DEBUG" ]]; then
|
||||
echo -e "\n=== DEBUG: Drive Mappings ==="
|
||||
for key in "${!DRIVE_MAP[@]}"; do
|
||||
echo "Bay $key: ${DRIVE_MAP[$key]}"
|
||||
done | sort -n
|
||||
fi
|
||||
Reference in New Issue
Block a user