10 Commits

Author SHA1 Message Date
145362b7ef Basic disk page implementation
+ Basic information about the disk
2024-01-22 23:12:15 +02:00
f17d8b44a3 Implemented View Button
* Updated Style
+ Added 2 icons for the view button
+ New `drive.html` page ( WIP )
* Updated `index.html` to apply updates
2024-01-22 23:01:05 +02:00
de08a7f970 Fixed major bug
* Resolved the hwmon not found problem
2024-01-22 12:39:31 +02:00
d6588742d3 Updated Information
* Updated `build.sh`
* Updated `README.md`
2024-01-22 12:24:27 +02:00
44d4237bec Fixed Static folder push 2024-01-22 02:34:27 +02:00
f2c84fb6b2 Fixed Dockerfile 2024-01-22 02:23:43 +02:00
4f50819f92 Production docker-compose.yml updates 2024-01-22 02:12:18 +02:00
6117157598 DockerFile link to repository
* Fixed issue
2024-01-22 02:01:39 +02:00
79e44bd88a + Production docker-compose.yml 2024-01-22 01:59:38 +02:00
c556e3827b Updated deploy 2024-01-22 01:58:43 +02:00
13 changed files with 255 additions and 31 deletions

8
.dockerignore Normal file
View File

@@ -0,0 +1,8 @@
/**
!main.go
!/lib
!/templates
!/static
!/go.mod
!/go.sum

View File

@@ -1,7 +1,9 @@
# Build Stage # Build Stage
FROM debian:bullseye FROM debian:bullseye-slim
ENV IS_DOCKER TRUE ENV IS_DOCKER TRUE
LABEL org.opencontainers.image.source https://github.com/JustKato/drive-health
# Install build dependencies and runtime dependencies # Install build dependencies and runtime dependencies
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
gcc \ gcc \
@@ -22,10 +24,9 @@ ENV PATH /usr/local/go/bin:$PATH
ENV GOPATH=/go ENV GOPATH=/go
ENV PATH=$GOPATH/bin:$PATH ENV PATH=$GOPATH/bin:$PATH
ENV GO111MODULE=on ENV GO111MODULE=on
ENV DIST_DIR=/app
# Create the directory and set it as the working directory # Create the directory and set it as the working directory
WORKDIR $DIST_DIR WORKDIR /app
# Copy the Go files and download dependencies # Copy the Go files and download dependencies
COPY go.mod go.sum ./ COPY go.mod go.sum ./

View File

@@ -1,29 +1,69 @@
## 📖About ## 📖 About
Drive Health is a program written in golang to help with tracking and monitoring of your hardware's temperature. Drive Health is a program written in golang to help with tracking and monitoring of your hardware's temperature.
This tool has been with the purpose of installing it in different servers I own with different configurations to help keep track of the temperature of different hard-disks, ssds, nvme drives, etc... The testing has been very limited to only 4 different computers and not on laptops so expect some mishaps. This tool had been conceived with the purpose of installing it on different servers I own with different configurations to help keep track of the temperature of hard-disks, ssds, nvme drives, etc...
### Features
- Disk Listing
- Temperature Graphing
- Disk activity logging
- [API](./lib/web/api.go)
![UI Example](./media/design_v1.webp) ![UI Example](./media/design_v1.webp)
## ❗Disclaimer ## ❗ Disclaimer
I'm not exactly a linux hardware wizard, so I honestly have no clue about a lot of things and I myself can tell there's a lot to improve upon and that there's a lot of other things missing that are a little bit more obscure, I personally don't currently own any m.2 sata drives to test the code on, or many of the other drive types, I have only tested on HDD, SSD and NVMe drives, any issues opened would help me so much! I'm not exactly a linux hardware wizard, so I honestly have no clue about a lot of things and I myself can tell there's a lot to improve upon and that there's a lot of other things missing that are a little bit more obscure, I personally don't currently own any m.2 sata drives to test the code on, or many of the other drive types, I have only tested on HDD, SSD and NVMe drives, any issues opened would help me so much!
## ❗Requirements ## ❗ Requirements
1. A linux machine, this will NOT work on macOS or on Windows, it's meant to be ran on servers as a service with which administrators can privately connect to for temperature logging. 1. A linux machine, this will NOT work on macOS or on Windows, it's meant to be ran on servers as a service with which administrators can privately connect to for temperature logging.
2. Please make sure you have the [**drivetemp kernel drive**](https://docs.kernel.org/hwmon/drivetemp.html) you can check this by running `sudo modprobe drivetemp`. 2. Please make sure you have the [**drivetemp kernel drive**](https://docs.kernel.org/hwmon/drivetemp.html) you can check this by running `sudo modprobe drivetemp`.
The program depends on this to be able to log the temperature of your devices. The program depends on this to be able to log the temperature of your devices.
## 📖How to use ## 📖 How to use
1. Follow the `Deployment` section instrcutions to launch the program
The program is straight forward to use really, edit the [.env](./.env) file and make the changes you would like applied. 2. Once the program has launched, access it in your browser
### Docker ( Recommended/Hassle free ) 3. Enter the administrative username and password for the simple HTTP Auth
4. You now have access to the application, you can monitor your disk's temperature over a period of time.
## 🐦 Deployment
To deploy the application you have multiple choices, the preffered method should be one which runs the binary directly and not containerization, the `docker` image is taking up a wopping `1Gb+` because I have to include sqlite3-dev and musl-dev dependencies, which sucks, so I whole heartedly recommend just installing this on your system as a binary either with `SystemD` or whichever service manager you are using.
Download binaries from [the releases page](https://github.com/JustKato/drive-health/releases)
### 🐋 Docker
In the project there's a `docker-compose.prod.yml` which you can deploy on your server, you will notice that there's also a "dev" version, this version simply has a `build` instead of `image` property, so feel free to use either.
Please do take notice that I have just fed the `environment file` directly to the service via docker-compose, and I recommend you do the same but please feel free to pass in `environment` variables straight to the process as well.
[Docker Compose File](./docker-compose.prod.yml)
```yaml
version: "3.8"
services:
drive-health:
# Latest image pull, mention the specific version here please.
image: ghcr.io/justkato/drive-health:latest
# Restart in case of crashing
restart: unless-stopped
# Load environment variables from .env file
env_file:
- .env
# Mount the volume to the local drive
volumes:
- ./data:/data
# Setup application ports
ports:
- 5003:8080
```
### 💾 SystemD
When running with SystemD or any other service manager, please make sure you have a `.env` inside the `WorkingDirectory` of your runner, in the below example I will simply put my env in `/home/daniel/services/drive-health/.env`
### SystemD
```ini ```ini
[Unit] [Unit]
Description=Drive Health Service Description=Drive Health Service
@@ -40,18 +80,19 @@ Restart=on-failure
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
## ❔FAQ ## ❔ FAQ
### How does it work? ### How does it work?
Currently the program does not depend on any hardware library as I couldn't find anything that would not require root access while giving me the possibility to interrogate the temperature of the drives, I chose not to depend on `lsblk` either, so how does the program work? Well it looks in `/sys/block` and simply Currently the program does not depend on any go library for hardware detection as I couldn't find anything that would not require root access while giving me the possibility to interrogate the temperature of the drives.
I chose not to depend on `lsblk` either, so how does the program work?
The program currently looks in `/sys/block` and then tries to make sense of the devices, I have had limited testing with my hardware specs, any issues being open in regards to different kinds of hardware would be highly appreciated
### Why not just run as root? ### Why not just run as root?
I really, really, **really** want to avoid asking people to run **ANY** program I write as root and even try and prevent that from happening since that's how things can go bad, especially because I am runnig actions over hardware items. I think you can see how easy it is for a mistake or a **malicious attack** to easily deal damage I really, REALLY, **REALLY** want to avoid asking people to run **ANY** program I write as root and even try and prevent that from happening since that's how things can go bad, especially because I am running actions over hardware devices.
## Support & Contribution ## Support & Contribution
For support, bug reports, or feature requests, please open an issue on the [GitHub repository](https://github.com/JustKato/drive-health/issues). Contributions are welcome! Fork the repository, make your changes, and submit a pull request. For support, bug reports, or feature requests, please open an issue on the [GitHub repository](https://github.com/JustKato/drive-health/issues). Contributions are welcome! Fork the repository, make your changes, and submit a pull request.
## License ## License
This project is licensed under the [Apache License 2.0](./LICENSE). This project is licensed under the [Apache License 2.0](./LICENSE).

View File

@@ -1,19 +1,50 @@
#!/bin/sh #!/usr/bin/env bash
set -o pipefail set -o pipefail
set -u set -u
# Function to display messages in color
echo_color() {
color=$1
text=$2
case $color in
"green") echo -e "\033[0;32m$text\033[0m" ;;
"yellow") echo -e "\033[0;33m$text\033[0m" ;;
"red") echo -e "\033[0;31m$text\033[0m" ;;
*) echo "$text" ;;
esac
}
# Getting GIT_VERSION from the most recent tag or commit hash
GIT_VERSION=$(git describe --tags --always)
if [ -z "$GIT_VERSION" ]; then
echo_color red "Error: Unable to determine GIT_VERSION."
exit 1
fi
APP_NAME="drive-health" APP_NAME="drive-health"
DIST_DIR="${DIST_DIR:-dist}" DIST_DIR="${DIST_DIR:-dist}"
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# make sure we are in the source dir
cd $SCRIPT_DIR;
# Create the dist directory if it doesn't exist # Create the dist directory if it doesn't exist
mkdir -p $DIST_DIR mkdir -p $DIST_DIR
# Build the application # Build the application
echo "Building the application..." echo_color yellow "[🦝] Building the application..."
GOOS=linux CGO_ENABLED=1 GOARCH=amd64 go build -o $DIST_DIR/$APP_NAME GOOS=linux CGO_ENABLED=1 GOARCH=amd64 go build -o $DIST_DIR/$APP_NAME
# echo "Copying additional resources..." # Copying additional resources...
cp -r static templates $DIST_DIR/ cp -r static templates $DIST_DIR/
echo "Compilation and packaging completed." echo_color yellow "[🦝] Compilation and packaging completed, archiving..."
cd $DIST_DIR/
zip "drive-health_$GIT_VERSION.zip" -r .
# TODO: Add reliable method of cleaning up the compiled files optionally
cd $SCRIPT_DIR;

View File

@@ -29,7 +29,8 @@ echo_color green "All tests passed successfully."
echo_color green "Starting the Docker build process with version $GIT_VERSION..." echo_color green "Starting the Docker build process with version $GIT_VERSION..."
IMAGE_NAME="ghcr.io/JustKato/drive-health:$GIT_VERSION" LATEST_IMAGE_NAME="ghcr.io/justkato/drive-health:latest"
IMAGE_NAME="ghcr.io/justkato/drive-health:$GIT_VERSION"
echo_color yellow "Image to be built: $IMAGE_NAME" echo_color yellow "Image to be built: $IMAGE_NAME"
# Confirmation to build # Confirmation to build
@@ -38,6 +39,10 @@ if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
# Building the Docker image # Building the Docker image
echo "Building Docker image: $IMAGE_NAME" echo "Building Docker image: $IMAGE_NAME"
docker build --no-cache -t $IMAGE_NAME . docker build --no-cache -t $IMAGE_NAME .
# Also tag this build as 'latest'
echo "Tagging image as latest: $LATEST_IMAGE_NAME"
docker tag $IMAGE_NAME $LATEST_IMAGE_NAME
else else
echo_color red "Build cancelled." echo_color red "Build cancelled."
exit 1 exit 1
@@ -49,6 +54,10 @@ if [[ "$push_response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
# Pushing the image # Pushing the image
echo "Pushing image: $IMAGE_NAME" echo "Pushing image: $IMAGE_NAME"
docker push $IMAGE_NAME docker push $IMAGE_NAME
# Pushing the 'latest' image
echo "Pushing latest image: $LATEST_IMAGE_NAME"
docker push $LATEST_IMAGE_NAME
else else
echo_color red "Push cancelled." echo_color red "Push cancelled."
fi fi

17
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,17 @@
version: "3.8"
services:
drive-health:
# Latest image pull, mention the specific version here please.
image: ghcr.io/justkato/drive-health:latest
# Restart in case of crashing
restart: unless-stopped
# Load environment variables from .env file
env_file:
- .env
# Mount the volume to the local drive
volumes:
- ./data:/data
# Setup application ports
ports:
- 5003:8080

View File

@@ -44,16 +44,19 @@ type Snapshots struct {
} }
func (h *HardDrive) GetTemperature() int { func (h *HardDrive) GetTemperature() int {
// Try HDD/SSD path
temp, found := h.getTemperatureFromPath("/sys/block/" + h.Name + "/device/hwmon/") possiblePaths := []string{
if found { "/sys/block/" + h.Name + "/device/hwmon/",
return temp "/sys/block/" + h.Name + "/device/",
"/sys/block/" + h.Name + "/device/generic/device/",
} }
// Try NVMe path for _, path := range possiblePaths {
temp, found = h.getTemperatureFromPath("/sys/block/" + h.Name + "/device/") // Try HDD/SSD path
if found { temp, found := h.getTemperatureFromPath(path)
return temp if found {
return temp
}
} }
fmt.Printf("[🛑] Failed to get temperature for %s\n", h.Name) fmt.Printf("[🛑] Failed to get temperature for %s\n", h.Name)

View File

@@ -15,6 +15,21 @@ func setupFrontend(r *gin.Engine) {
r.LoadHTMLGlob("templates/*") r.LoadHTMLGlob("templates/*")
r.Static("/static", "./static") r.Static("/static", "./static")
r.GET("/disk/:id", func(ctx *gin.Context) {
id := ctx.Param("id")
var hdd hardware.HardDrive
tx := svc.GetDatabaseRef().Where("id = ?", id).Preload("Temperatures").First(&hdd)
if tx.Error != nil {
ctx.AbortWithError(500, tx.Error)
return
}
// Render the HTML template
ctx.HTML(http.StatusOK, "drive.html", gin.H{
"hdd": hdd,
})
})
// Set up a route for the root URL // Set up a route for the root URL
r.GET("/", func(ctx *gin.Context) { r.GET("/", func(ctx *gin.Context) {
hardDrives, err := hardware.GetSystemHardDrives(svc.GetDatabaseRef(), nil, nil) hardDrives, err := hardware.GetSystemHardDrives(svc.GetDatabaseRef(), nil, nil)

View File

@@ -11,14 +11,17 @@
--fg1: #434c56; --fg1: #434c56;
--acc1: #2aa3f4; --acc1: #2aa3f4;
--acc1H: #0089e5;
--acc1BG0: #2aa3f450; --acc1BG0: #2aa3f450;
--acc1BG1: #2aa3f430; --acc1BG1: #2aa3f430;
--acc2: #2af488; --acc2: #2af488;
--acc2H: #2af488;
--acc2BG0: #2af48850; --acc2BG0: #2af48850;
--acc2BG1: #2af48830; --acc2BG1: #2af48830;
--acc3: #f4e02a; --acc3: #f4e02a;
--acc3H: #f4e02a;
--acc3BG0: #f4e02a50; --acc3BG0: #f4e02a50;
--acc3BG1: #f4e02a30; --acc3BG1: #f4e02a30;
} }
@@ -28,6 +31,17 @@
font-family: "Noto Sans Mono", "Roboto", sans-serif; font-family: "Noto Sans Mono", "Roboto", sans-serif;
} }
a {
color: inherit;
text-decoration: inherit;
}
a:hover {
color: inherit;
text-decoration: inherit;
}
html, body { html, body {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -155,6 +169,32 @@ input {
background-color: var(--bg3); background-color: var(--bg3);
} }
.btn-link {
color: var(--acc1);
}
.btn-link:hover {
color: var(--acc1H);
}
.info-button {
display: flex;
mask-image: url("/static/view-svgrepo-com.svg");
background: var(--fg0);
height: 26px;
width: 26px;
mask-size: contain;
mask-position: center;
mask-repeat: no-repeat;
}
.info-button:hover {
mask-image: url("/static/view-alt-svgrepo-com.svg");
background-color: var(--acc1);
}
.input-grp { .input-grp {
display: flex; display: flex;
flex-flow: column; flex-flow: column;

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 4H17.2C18.9913 4 19.887 4 20.4435 4.5565C21 5.11299 21 6.00866 21 7.8V8M17 20H17.2C18.9913 20 19.887 20 20.4435 19.4435C21 18.887 21 17.9913 21 16.2V16M7 4H6.8C5.00866 4 4.11299 4 3.5565 4.5565C3 5.11299 3 6.00866 3 7.8V8M7 20H6.8C5.00866 20 4.11299 20 3.5565 19.4435C3 18.887 3 17.9913 3 16.2V16" stroke="#33363F" stroke-width="2" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.8149 12C18.8149 11.4637 18.6892 11.2462 18.4379 10.8112C17.5834 9.33247 15.6561 7 12 7C8.34395 7 6.41664 9.33247 5.56212 10.8112C5.31077 11.2462 5.18509 11.4637 5.18509 12C5.18509 12.5363 5.31077 12.7538 5.56212 13.1888C6.41664 14.6675 8.34395 17 12 17C15.6561 17 17.5834 14.6675 18.4379 13.1888C18.6892 12.7538 18.8149 12.5363 18.8149 12ZM12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3432 9 9.00001 10.3431 9.00001 12C9.00001 13.6569 10.3432 15 12 15Z" fill="#33363F"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7703 12C20.7703 11.6412 20.5762 11.4056 20.188 10.9343C18.768 9.21014 15.6357 6 12 6C8.36428 6 5.23207 9.21014 3.81198 10.9343C3.42382 11.4056 3.22974 11.6412 3.22974 12C3.22974 12.3588 3.42382 12.5944 3.81198 13.0657C5.23207 14.7899 8.36428 18 12 18C15.6357 18 18.768 14.7899 20.188 13.0657C20.5762 12.5944 20.7703 12.3588 20.7703 12ZM12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3432 9 9.00002 10.3431 9.00002 12C9.00002 13.6569 10.3432 15 12 15Z" fill="#33363F"/>
</svg>

After

Width:  |  Height:  |  Size: 767 B

46
templates/drive.html Normal file
View File

@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
<title>Drive Health Dashboard</title>
</head>
<body>
<div class="container bordered">
<div class="container-titlebar">
<div class="pad">
<h4>{{ .hdd.Model }} <span class="grooved">{{ .hdd.Size }}</span></h4>
</div>
</div>
<div class="container-body">
<div class="pad">
<table id="disks-table">
<thead>
<tr>
<td>ID</td>
<td>Name</td>
<td>Model</td>
<td>Serial</td>
<td>Temperature</td>
</tr>
</thead>
<tbody id="disk-table-body">
<tr>
<td>#{{ .hdd.ID }}</td>
<td> {{ .hdd.Name }}</td>
<td> {{ .hdd.Model }}</td>
<td> {{ .hdd.Serial }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

View File

@@ -30,6 +30,7 @@
<td>Model</td> <td>Model</td>
<td>Serial</td> <td>Serial</td>
<td>Temperature</td> <td>Temperature</td>
<td>Actions</td>
</tr> </tr>
</thead> </thead>
<tbody id="disk-table-body"> <tbody id="disk-table-body">
@@ -49,6 +50,9 @@
{{ else }} <!-- Temperature 30°C or below --> {{ else }} <!-- Temperature 30°C or below -->
<td style="color: lime;">{{ $temp }}&deg;C</td> <td style="color: lime;">{{ $temp }}&deg;C</td>
{{ end }} {{ end }}
<td>
<a title="View Disk" class="info-button" href="/disk/{{ .ID }}"></a>
</td>
</tr> </tr>
{{ end }} {{ end }}
</tbody> </tbody>