diff --git a/drive-health/Dockerfile b/drive-health/Dockerfile deleted file mode 100644 index 13a6f44..0000000 --- a/drive-health/Dockerfile +++ /dev/null @@ -1,65 +0,0 @@ -# === Build Stage === -FROM debian:bullseye-slim AS builder -ENV IS_DOCKER TRUE - -LABEL org.opencontainers.image.source https://github.com/JustKato/drive-health - -# Install build dependencies and runtime dependencies -RUN apt-get update && apt-get install -y \ - gcc \ - musl-dev \ - libsqlite3-dev \ - libsqlite3-0 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -# Manually install Go 1.21 -ENV GOLANG_VERSION 1.21.0 -RUN wget https://golang.org/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz -O go.tgz \ - && tar -C /usr/local -xzf go.tgz \ - && rm go.tgz -ENV PATH /usr/local/go/bin:$PATH - -# Set the environment variable for Go -ENV GOPATH=/go -ENV PATH=$GOPATH/bin:$PATH -ENV GO111MODULE=on - -# Create the directory and set it as the working directory -WORKDIR /app - -# Copy the Go files and download dependencies -COPY go.mod go.sum ./ -RUN go mod download - -# Copy the source from the current directory to the Working Directory inside the container -COPY . . - -# Build the Go app -RUN go build -o drive-health - -# Cleanup build dependencies to reduce image size -RUN apt-get purge -y gcc musl-dev libsqlite3-dev wget \ - && apt-get autoremove -y \ - && apt-get clean - -# === Final Stage === -FROM debian:bullseye-slim AS final - -# Set the environment variable -ENV IS_DOCKER TRUE - -# Create the directory and set it as the working directory -WORKDIR /app - -# Copy only the necessary files from the builder stage -COPY --from=builder /app/drive-health . - -# Expose the necessary port -EXPOSE 8080 - -# Volume for external data -VOLUME [ "/data" ] - -# Command to run the executable -CMD ["./drive-health"] diff --git a/drive-health/LICENSE b/drive-health/LICENSE deleted file mode 100644 index f738181..0000000 --- a/drive-health/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright [2024] [Daniel Legt] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/drive-health/README.md b/drive-health/README.md deleted file mode 100644 index 3cdc5ad..0000000 --- a/drive-health/README.md +++ /dev/null @@ -1,98 +0,0 @@ -## 📖 About -Drive Health is a program written in golang to help with tracking and monitoring of your hardware's temperature. - -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) - -## ❗ 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! - -## ❗ 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. - -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. - - -## 📖 How to use -1. Follow the `Deployment` section instrcutions to launch the program - -2. Once the program has launched, access it in your browser - -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` - -```ini -[Unit] -Description=Drive Health Service -After=network.target - -[Service] -Type=simple -User=daniel # Your user here -WorkingDirectory=/home/daniel/services/drive-health # The path to the service's directory -ExecStart=/home/daniel/services/drive-health/drive-health # The path to the binary -Restart=on-failure - -[Install] -WantedBy=multi-user.target -``` - -## ❔ FAQ - -### How does it work? -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? -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 -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 -This project is licensed under the [Apache License 2.0](./LICENSE). \ No newline at end of file diff --git a/drive-health/build.sh b/drive-health/build.sh deleted file mode 100644 index 0906293..0000000 --- a/drive-health/build.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -set -o pipefail -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" -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 -mkdir -p $DIST_DIR - -# Build the application -echo_color yellow "[🦝] Building the application..." -GOOS=linux CGO_ENABLED=1 GOARCH=amd64 go build -o $DIST_DIR/$APP_NAME - -# Copying additional resources... -cp -r static templates $DIST_DIR/ - -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; \ No newline at end of file diff --git a/drive-health/deploy.sh b/drive-health/deploy.sh deleted file mode 100644 index 51a0b85..0000000 --- a/drive-health/deploy.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -# 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 - -# Run tests before proceeding -echo_color yellow "Running tests..." -if ! go test; then - echo_color red "Tests failed. Cancelling build process." - exit 1 -fi -echo_color green "All tests passed successfully." - -echo_color green "Starting the Docker build process with version $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" - -# Confirmation to build -read -p "Are you sure you want to build an image? (y/N) " response -if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - # Building the Docker image - echo "Building Docker image: $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 - echo_color red "Build cancelled." - exit 1 -fi - -# Prompt to push the image -read -p "Push image to repository? (y/N) " push_response -if [[ "$push_response" =~ ^([yY][eE][sS]|[yY])$ ]]; then - # Pushing the image - echo "Pushing image: $IMAGE_NAME" - docker push $IMAGE_NAME - - # Pushing the 'latest' image - echo "Pushing latest image: $LATEST_IMAGE_NAME" - docker push $LATEST_IMAGE_NAME -else - echo_color red "Push cancelled." -fi - -echo_color green "Ending the Docker build process..." \ No newline at end of file diff --git a/drive-health/docker-compose.dev.yml b/drive-health/docker-compose.dev.yml deleted file mode 100644 index 62bc990..0000000 --- a/drive-health/docker-compose.dev.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: "3.8" - -services: - drive-health: - # Build the current image - build: . - # Read straight from the .env file, or use the environment path - env_file: - - .env - volumes: - - ./dev_data:/data - # Setup application ports - ports: - - 8080:8080 \ No newline at end of file diff --git a/drive-health/docker-compose.prod.yml b/drive-health/docker-compose.prod.yml deleted file mode 100644 index bb00926..0000000 --- a/drive-health/docker-compose.prod.yml +++ /dev/null @@ -1,17 +0,0 @@ -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 \ No newline at end of file diff --git a/drive-health/go.mod b/drive-health/go.mod deleted file mode 100644 index e45c300..0000000 --- a/drive-health/go.mod +++ /dev/null @@ -1,45 +0,0 @@ -module github.com/JustKato/drive-health - -go 1.21.6 - -require ( - github.com/gin-gonic/gin v1.9.1 - github.com/joho/godotenv v1.5.1 - github.com/wcharczuk/go-chart/v2 v2.1.1 - gorm.io/driver/sqlite v1.5.4 - gorm.io/gorm v1.25.5 -) - -require ( - github.com/blend/go-sdk v1.20220411.3 // indirect - github.com/bytedance/sonic v1.10.2 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.17.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.19 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/image v0.11.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/drive-health/go.sum b/drive-health/go.sum deleted file mode 100644 index da6a1ef..0000000 --- a/drive-health/go.sum +++ /dev/null @@ -1,141 +0,0 @@ -github.com/blend/go-sdk v1.20220411.3 h1:GFV4/FQX5UzXLPwWV03gP811pj7B8J2sbuq+GJQofXc= -github.com/blend/go-sdk v1.20220411.3/go.mod h1:7lnH8fTi6U4i1fArEXRyOIY2E1X4MALg09qsQqY1+ak= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= -github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE= -github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= -gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/drive-health/lib/config/config.go b/drive-health/lib/config/config.go deleted file mode 100644 index 2794b37..0000000 --- a/drive-health/lib/config/config.go +++ /dev/null @@ -1,95 +0,0 @@ -package config - -import ( - "os" - "strconv" -) - -type DHConfig struct { - CleanupServiceFrequency int `json:"cleanupServiceFrequency"` - DiskFetchFrequency int `json:"diskFetchFrequency"` - MaxHistoryAge int `json:"maxHistoryAge"` - - DatabaseFilePath string `json:"databaseFilePath"` - - Listen string `json:"listen"` - - IdentityUsername string `json:"identityUsername"` - IdentityPassword string `json:"identityPassword"` - - IsDocker bool `json:isDocker` - - DebugMode bool `json:"debugMode"` -} - -var config *DHConfig = nil - -func GetConfiguration() *DHConfig { - - if config != nil { - return config - } - - config = &DHConfig{ - DiskFetchFrequency: 5, - CleanupServiceFrequency: 3600, - MaxHistoryAge: 2592000, - DatabaseFilePath: "./data.sqlite", - IdentityUsername: "admin", - IdentityPassword: "admin", - - IsDocker: false, - - Listen: ":8080", - } - - if val, exists := os.LookupEnv("DISK_FETCH_FREQUENCY"); exists { - if intValue, err := strconv.Atoi(val); err == nil { - config.DiskFetchFrequency = intValue - } - } - - if val, exists := os.LookupEnv("CLEANUP_SERVICE_FREQUENCY"); exists { - if intValue, err := strconv.Atoi(val); err == nil { - config.CleanupServiceFrequency = intValue - } - } - - if val, exists := os.LookupEnv("MAX_HISTORY_AGE"); exists { - if intValue, err := strconv.Atoi(val); err == nil { - config.MaxHistoryAge = intValue - } - } - - if val, exists := os.LookupEnv("LISTEN"); exists { - config.Listen = val - } - - if val, exists := os.LookupEnv("DATABASE_FILE_PATH"); exists { - config.DatabaseFilePath = val - } - - if val, exists := os.LookupEnv("IDENTITY_USERNAME"); exists { - config.IdentityUsername = val - } - - if val, exists := os.LookupEnv("IDENTITY_PASSWORD"); exists { - config.IdentityPassword = val - } - - if val, exists := os.LookupEnv("DEBUG_MODE"); exists { - if isDebug, err := strconv.ParseBool(val); err == nil { - config.DebugMode = isDebug - } - } - - if val, exists := os.LookupEnv("IS_DOCKER"); exists { - if isDocker, err := strconv.ParseBool(val); err == nil { - config.IsDocker = isDocker - - config.DatabaseFilePath = "/data/data.sqlite" - } - } - - return config -} diff --git a/drive-health/lib/hardware/logic.go b/drive-health/lib/hardware/logic.go deleted file mode 100644 index afad9b1..0000000 --- a/drive-health/lib/hardware/logic.go +++ /dev/null @@ -1,198 +0,0 @@ -package hardware - -import ( - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/JustKato/drive-health/lib/config" - "gorm.io/gorm" -) - -func GetSystemHardDrives(db *gorm.DB, olderThan *time.Time, newerThan *time.Time) ([]*HardDrive, error) { - var systemHardDrives []*HardDrive - - // List all block devices - devices, err := os.ReadDir("/sys/block/") - if err != nil { - return nil, fmt.Errorf("failed to list block devices: %w", err) - } - - for _, device := range devices { - deviceName := device.Name() - - // Skip non-physical devices (like loop and ram devices) - // TODO: Read more about this, there might be some other devices we should or should not skip - if strings.HasPrefix(deviceName, "loop") || strings.HasPrefix(deviceName, "ram") { - continue - } - - // Read device details - model, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/device/model", deviceName)) - serial, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/device/serial", deviceName)) - sizeBytes, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/size", deviceName)) - - size := convertSizeToString(sizeBytes) - transport := getTransportType(deviceName) - - // TODO: Maybe find a better way? - if size == "0 Bytes" { - // This looks like an invalid device, skip it. - if config.GetConfiguration().DebugMode { - fmt.Printf("[🟨] Igoring device:[/dev/%s], reported size of 0\n", deviceName) - } - continue - } - - hwid, err := getHardwareID(deviceName) - if err != nil { - if config.GetConfiguration().DebugMode { - fmt.Printf("[🟨] No unique identifier found for device:[/dev/%s] unique identifier\n", deviceName) - } - continue - } - - hd := &HardDrive{ - Name: deviceName, - Transport: transport, - Model: strings.TrimSpace(string(model)), - Serial: strings.TrimSpace(string(serial)), - Size: size, - Type: getDriveType(deviceName), - HWID: hwid, - } - - systemHardDrives = append(systemHardDrives, hd) - } - - var updatedHardDrives []*HardDrive - - for _, sysHDD := range systemHardDrives { - var existingHD HardDrive - q := db.Where("hw_id = ?", sysHDD.HWID) - - if newerThan != nil && olderThan != nil { - q = q.Preload("Temperatures", "time_stamp < ? AND time_stamp > ?", newerThan, olderThan) - } - - result := q.First(&existingHD) - - if result.Error == gorm.ErrRecordNotFound { - // Hard drive not found, create new - db.Create(&sysHDD) - updatedHardDrives = append(updatedHardDrives, sysHDD) - } else { - // Hard drive found, update existing - existingHD.Name = sysHDD.Name - existingHD.Transport = sysHDD.Transport - existingHD.Size = sysHDD.Size - existingHD.Model = sysHDD.Model - existingHD.Type = sysHDD.Type - db.Save(&existingHD) - updatedHardDrives = append(updatedHardDrives, &existingHD) - } - } - - return updatedHardDrives, nil -} - -func getTransportType(deviceName string) string { - transportLink, err := filepath.EvalSymlinks(fmt.Sprintf("/sys/block/%s/device", deviceName)) - if err != nil { - return "Unknown" - } - - if strings.Contains(transportLink, "/usb/") { - return "USB" - } else if strings.Contains(transportLink, "/ata") { - return "SATA" - } else if strings.Contains(transportLink, "/nvme/") { - return "NVMe" - } - - return "Other" -} - -func convertSizeToString(sizeBytes []byte) string { - // Convert the size from a byte slice to a string, then to an integer - sizeStr := strings.TrimSpace(string(sizeBytes)) - sizeSectors, err := strconv.ParseInt(sizeStr, 10, 64) - if err != nil { - return "Unknown" - } - - // Convert from 512-byte sectors to bytes - sizeInBytes := sizeSectors * 512 - - // Define size units - const ( - _ = iota // ignore first value by assigning to blank identifier - KB float64 = 1 << (10 * iota) - MB - GB - TB - ) - - var size float64 = float64(sizeInBytes) - var unit string - - // Determine the unit to use - switch { - case size >= TB: - size /= TB - unit = "TB" - case size >= GB: - size /= GB - unit = "GB" - case size >= MB: - size /= MB - unit = "MB" - case size >= KB: - size /= KB - unit = "KB" - default: - unit = "Bytes" - } - - // Return the formatted size - return fmt.Sprintf("%.2f %s", size, unit) -} - -// Look throug /sys/block/device/ and try and find the unique identifier of the device. -func getHardwareID(deviceName string) (string, error) { - // Define potential ID file paths - idFilePaths := []string{ - "/sys/block/" + deviceName + "/device/wwid", - "/sys/block/" + deviceName + "/device/wwn", - "/sys/block/" + deviceName + "/device/serial", - } - - // Try to read each file and return the first successful read - for _, path := range idFilePaths { - if idBytes, err := os.ReadFile(path); err == nil { - return strings.TrimSpace(string(idBytes)), nil - } - } - - // Return an empty string if no ID is found - return "", fmt.Errorf("could not find unique identifier for %s", deviceName) -} - -// Figure out what kind of device this is by reading if it's rotational or not -func getDriveType(deviceName string) string { - // Check if the drive is rotational (HDD) - if isRotational, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/queue/rotational", deviceName)); string(isRotational) == "1\n" { - return "HDD" - } - - // Check if the drive is NVMe - if strings.HasPrefix(deviceName, "nvme") { - return "NVMe" - } - - // Default to SSD for non-rotational and non-NVMe drives - return "SSD" -} diff --git a/drive-health/lib/hardware/models.go b/drive-health/lib/hardware/models.go deleted file mode 100644 index cebf906..0000000 --- a/drive-health/lib/hardware/models.go +++ /dev/null @@ -1,94 +0,0 @@ -package hardware - -import ( - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "gorm.io/gorm" -) - -type HardDrive struct { - ID uint `gorm:"primarykey"` - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` - Name string - Transport string - Size string - Model string - Serial string - Type string - HWID string - Temperatures []HardDriveTemperature `gorm:"foreignKey:HardDriveID"` -} - -type HardDriveTemperature struct { - gorm.Model - HardDriveID uint - TimeStamp time.Time - Temperature int -} - -// A snapshot in time of the current state of the harddrives -type HardwareSnapshot struct { - TimeStamp time.Time - HDD []*HardDrive -} - -type Snapshots struct { - List []*HardwareSnapshot -} - -func (h *HardDrive) GetTemperature() int { - - possiblePaths := []string{ - "/sys/block/" + h.Name + "/device/hwmon/", - "/sys/block/" + h.Name + "/device/", - "/sys/block/" + h.Name + "/device/generic/device/", - } - - for _, path := range possiblePaths { - // Try HDD/SSD path - temp, found := h.getTemperatureFromPath(path) - if found { - return temp - } - } - - fmt.Printf("[🛑] Failed to get temperature for %s\n", h.Name) - return -1 -} - -func (h *HardDrive) getTemperatureFromPath(basePath string) (int, bool) { - hwmonDirs, err := os.ReadDir(basePath) - if err != nil { - return 0, false - } - - for _, dir := range hwmonDirs { - if strings.HasPrefix(dir.Name(), "hwmon") { - tempPath := filepath.Join(basePath, dir.Name(), "temp1_input") - if _, err := os.Stat(tempPath); err == nil { - tempBytes, err := os.ReadFile(tempPath) - if err != nil { - continue - } - - tempStr := strings.TrimSpace(string(tempBytes)) - temperature, err := strconv.Atoi(tempStr) - if err != nil { - continue - } - - // Convert millidegree Celsius to degree Celsius - return temperature / 1000, true - } - } - } - - return 0, false -} diff --git a/drive-health/lib/svc/service.go b/drive-health/lib/svc/service.go deleted file mode 100644 index 3c4c76c..0000000 --- a/drive-health/lib/svc/service.go +++ /dev/null @@ -1,186 +0,0 @@ -package svc - -import ( - "bytes" - "fmt" - "time" - - "github.com/JustKato/drive-health/lib/config" - "github.com/JustKato/drive-health/lib/hardware" - "github.com/wcharczuk/go-chart/v2" - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -var db *gorm.DB - -// Initialize the database connection -func InitDB() { - var err error - dbPath := config.GetConfiguration().DatabaseFilePath - - db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) - if err != nil { - // This should basically never happen, unless the path to the database - // is inaccessible, doesn't exist or there's no permission to it, which - // should and will crash the program - panic("failed to connect database") - } - - // Migrate the schema - db.AutoMigrate(&hardware.HardDrive{}, &hardware.HardDriveTemperature{}) -} - -// Fetch the open database pointer -func GetDatabaseRef() *gorm.DB { - return db -} - -// Log the temperature of the disks -func LogDriveTemps() error { - drives, err := hardware.GetSystemHardDrives(db, nil, nil) - if err != nil { - return err - } - - for _, hdd := range drives { - temp := hdd.GetTemperature() - db.Create(&hardware.HardDriveTemperature{ - HardDriveID: hdd.ID, - TimeStamp: time.Now(), - Temperature: temp, - }) - } - - return nil -} - -// Run the logging service, this will periodically log the temperature of the disks with the LogDriveTemps function -func RunLoggerService() { - fmt.Println("[🦝] Initializing Temperature Logging Service...") - - tickTime := time.Duration(config.GetConfiguration().DiskFetchFrequency) * time.Second - - // Snapshot taking routine - go func() { - for { - time.Sleep(tickTime) - err := LogDriveTemps() - if err != nil { - fmt.Printf("[🛑] Temperature logging failed: %s\n", err) - } - } - }() -} - -// Generate a PNG based upon a HDD id and a date range -func GetDiskGraphImage(hddID int, newerThan *time.Time, olderThan *time.Time) (*bytes.Buffer, error) { - var hdd hardware.HardDrive - // Fetch by a combination of fields - q := db.Where("id = ?", hddID) - - if newerThan == nil || olderThan == nil { - q = q.Preload("Temperatures") - } else { - q = q.Preload("Temperatures", "time_stamp < ? AND time_stamp > ?", newerThan, olderThan) - } - - // Query for the instance - result := q.First(&hdd) - if result.Error != nil { - return nil, result.Error - } - - // Prepare slices for X (time) and Y (temperature) values - var xValues []time.Time - var yValues []float64 - for _, temp := range hdd.Temperatures { - xValues = append(xValues, temp.TimeStamp) - yValues = append(yValues, float64(temp.Temperature)) - } - - // Allocate a buffer for the graph image - graphImageBuffer := bytes.NewBuffer([]byte{}) - - // TODO: Graph dark theme - - // Generate the chart - graph := chart.Chart{ - Title: fmt.Sprintf("%s:%s[%s]", hdd.Name, hdd.Serial, hdd.Size), - TitleStyle: chart.Style{ - FontSize: 14, - }, - - // TODO: Implement customizable sizing - Width: 1280, - - Background: chart.Style{ - Padding: chart.Box{ - Top: 20, Right: 20, Bottom: 20, Left: 20, - }, - }, - - XAxis: chart.XAxis{ - Name: "Time", - ValueFormatter: func(v interface{}) string { - if ts, isValidTime := v.(float64); isValidTime { - t := time.Unix(int64(ts/1e9), 0) - - return t.Format("Jan 2 2006, 15:04") - } - - return "" - }, - Style: chart.Style{}, - GridMajorStyle: chart.Style{ - StrokeColor: chart.ColorAlternateGray, - StrokeWidth: 0.5, - }, - GridMinorStyle: chart.Style{ - StrokeColor: chart.ColorAlternateGray.WithAlpha(64), - StrokeWidth: 0.25, - }, - }, - YAxis: chart.YAxis{ - Name: "Temperature (C)", - Style: chart.Style{}, - GridMajorStyle: chart.Style{ - StrokeColor: chart.ColorAlternateGray, - StrokeWidth: 0.5, - }, - GridMinorStyle: chart.Style{ - StrokeColor: chart.ColorAlternateGray.WithAlpha(64), - StrokeWidth: 0.25, - }, - }, - Series: []chart.Series{ - chart.TimeSeries{ - Name: "Temperature", - XValues: xValues, - YValues: yValues, - Style: chart.Style{ - StrokeColor: chart.ColorCyan, - StrokeWidth: 2.0, - }, - }, - }, - } - - // Add a legend to the chart - graph.Elements = []chart.Renderable{ - chart.Legend(&graph, chart.Style{ - Padding: chart.Box{ - Top: 5, Right: 5, Bottom: 5, Left: 5, - }, - FontSize: 10, - }), - } - - // Render the chart into the byte buffer - err := graph.Render(chart.PNG, graphImageBuffer) - if err != nil { - return nil, err - } - - return graphImageBuffer, nil -} diff --git a/drive-health/lib/svc/service_cleanup.go b/drive-health/lib/svc/service_cleanup.go deleted file mode 100644 index 79261ac..0000000 --- a/drive-health/lib/svc/service_cleanup.go +++ /dev/null @@ -1,45 +0,0 @@ -package svc - -import ( - "fmt" - "time" - - "github.com/JustKato/drive-health/lib/config" - "github.com/JustKato/drive-health/lib/hardware" -) - -// Delete all thermal entries that are older than X amount of seconds -func CleanupOldData() error { - cfg := config.GetConfiguration() - - beforeDate := time.Now().Add(-1 * time.Duration(cfg.MaxHistoryAge) * time.Second) - - deleteResult := db.Where("time_stamp < ?", beforeDate).Delete(&hardware.HardDriveTemperature{}) - if deleteResult.Error != nil { - fmt.Printf("[🛑] Error during cleanup: %s\n", deleteResult.Error) - return db.Error - } - - if deleteResult.RowsAffected > 0 { - fmt.Printf("[🛑] Cleaned up %v entries before %s\n", deleteResult.RowsAffected, beforeDate) - } - - return nil -} - -func RunCleanupService() { - fmt.Println("[🦝] Initializing Log Cleanup Service...") - - tickTime := time.Duration(config.GetConfiguration().CleanupServiceFrequency) * time.Second - - // Snapshot taking routine - go func() { - for { - time.Sleep(tickTime) - err := CleanupOldData() - if err != nil { - fmt.Printf("🛑 Cleanup process failed: %s\n", err) - } - } - }() -} diff --git a/drive-health/lib/web/api.go b/drive-health/lib/web/api.go deleted file mode 100644 index fd506b1..0000000 --- a/drive-health/lib/web/api.go +++ /dev/null @@ -1,95 +0,0 @@ -package web - -import ( - "net/http" - "strconv" - "time" - - "github.com/JustKato/drive-health/lib/hardware" - "github.com/JustKato/drive-health/lib/svc" - "github.com/gin-gonic/gin" -) - -func setupApi(r *gin.Engine) { - api := r.Group("/api/v1") - - // Fetch the chart image for the disk's temperature - api.GET("/disks/:diskid/chart", func(ctx *gin.Context) { - diskIDString := ctx.Param("diskid") - diskId, err := strconv.Atoi(diskIDString) - if err != nil { - ctx.AbortWithStatusJSON(400, gin.H{ - "error": err.Error(), - "message": "Invalid Disk ID", - }) - - return - } - - var olderThan, newerThan *time.Time - - if ot := ctx.Query("older"); ot != "" { - if otInt, err := strconv.ParseInt(ot, 10, 64); err == nil { - otTime := time.UnixMilli(otInt) - olderThan = &otTime - } - } - - if nt := ctx.Query("newer"); nt != "" { - if ntInt, err := strconv.ParseInt(nt, 10, 64); err == nil { - ntTime := time.UnixMilli(ntInt) - newerThan = &ntTime - } - } - - graphData, err := svc.GetDiskGraphImage(diskId, newerThan, olderThan) - if err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "error": err.Error(), - "message": "Graph generation issue", - }) - - return - } - - // Set the content type header - ctx.Writer.Header().Set("Content-Type", "image/png") - - // Write the image data to the response - ctx.Writer.WriteHeader(http.StatusOK) - _, err = graphData.WriteTo(ctx.Writer) - if err != nil { - ctx.AbortWithStatusJSON(500, gin.H{ - "error": err.Error(), - "message": "Write error", - }) - - return - } - }) - - // Get a list of all the disks - api.GET("/disks", func(ctx *gin.Context) { - - olderThan := time.Now().Add(time.Minute * time.Duration(10) * -1) - newerThan := time.Now() - - // Fetch the disk list - disks, err := hardware.GetSystemHardDrives(svc.GetDatabaseRef(), &olderThan, &newerThan) - if err != nil { - ctx.Error(err) - } - - if ctx.Request.URL.Query().Get("temp") != "" { - for _, d := range disks { - d.GetTemperature() - } - } - - ctx.JSON(http.StatusOK, gin.H{ - "message": "Disk List", - "disks": disks, - }) - }) - -} diff --git a/drive-health/lib/web/auth_middleware.go b/drive-health/lib/web/auth_middleware.go deleted file mode 100644 index ce27d00..0000000 --- a/drive-health/lib/web/auth_middleware.go +++ /dev/null @@ -1,11 +0,0 @@ -package web - -import "github.com/gin-gonic/gin" - -func BasicAuthMiddleware(username, password string) gin.HandlerFunc { - authorized := gin.Accounts{ - username: password, - } - - return gin.BasicAuth(authorized) -} diff --git a/drive-health/lib/web/frontend.go b/drive-health/lib/web/frontend.go deleted file mode 100644 index f0f65cf..0000000 --- a/drive-health/lib/web/frontend.go +++ /dev/null @@ -1,66 +0,0 @@ -package web - -import ( - "fmt" - "net/http" - "strconv" - "time" - - "github.com/JustKato/drive-health/lib/hardware" - "github.com/JustKato/drive-health/lib/svc" - "github.com/gin-gonic/gin" -) - -func setupFrontend(r *gin.Engine) { - r.LoadHTMLGlob("templates/*") - r.Static("/static", "./static") - - // Set up a route for the root URL - r.GET("/", func(ctx *gin.Context) { - hardDrives, err := hardware.GetSystemHardDrives(svc.GetDatabaseRef(), nil, nil) - if err != nil { - ctx.AbortWithStatus(500) - } - - for _, hdd := range hardDrives { - hdd.GetTemperature() - } - - var olderThan, newerThan *time.Time - - if ot := ctx.Query("older"); ot != "" { - fmt.Printf("ot = %s\n", ot) - if otInt, err := strconv.ParseInt(ot, 10, 64); err == nil { - otTime := time.UnixMilli(otInt) - olderThan = &otTime - } - } - - if nt := ctx.Query("newer"); nt != "" { - fmt.Printf("nt = %s\n", nt) - if ntInt, err := strconv.ParseInt(nt, 10, 64); err == nil { - ntTime := time.UnixMilli(ntInt) - newerThan = &ntTime - } - } - - if olderThan == nil { - genTime := time.Now().Add(time.Hour * -1) - - olderThan = &genTime - } - - if newerThan == nil { - genTime := time.Now() - - newerThan = &genTime - } - - // Render the HTML template - ctx.HTML(http.StatusOK, "index.html", gin.H{ - "drives": hardDrives, - "older": olderThan.UnixMilli(), - "newer": newerThan.UnixMilli(), - }) - }) -} diff --git a/drive-health/lib/web/health.go b/drive-health/lib/web/health.go deleted file mode 100644 index adcd54a..0000000 --- a/drive-health/lib/web/health.go +++ /dev/null @@ -1,16 +0,0 @@ -package web - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -func setupHealth(r *gin.Engine) { - - r.GET("/ping", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "status": "Pong", - }) - }) -} diff --git a/drive-health/lib/web/net.go b/drive-health/lib/web/net.go deleted file mode 100644 index 5e667d9..0000000 --- a/drive-health/lib/web/net.go +++ /dev/null @@ -1,29 +0,0 @@ -package web - -import ( - "github.com/JustKato/drive-health/lib/config" - "github.com/gin-gonic/gin" -) - -func SetupRouter() *gin.Engine { - cfg := config.GetConfiguration() - - if !cfg.DebugMode { - // Set gin to release - gin.SetMode(gin.ReleaseMode) - } - - // Initialize the Gin engine - r := gin.Default() - - r.Use(BasicAuthMiddleware(cfg.IdentityUsername, cfg.IdentityPassword)) - - // Setup Health Pings - setupHealth(r) - // Setup Api - setupApi(r) - // Setup Frontend - setupFrontend(r) - - return r -} diff --git a/drive-health/main.go b/drive-health/main.go deleted file mode 100644 index 886aeaa..0000000 --- a/drive-health/main.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/JustKato/drive-health/lib/config" - "github.com/JustKato/drive-health/lib/svc" - "github.com/JustKato/drive-health/lib/web" - "github.com/joho/godotenv" -) - -func main() { - // Load .env file if it exists - if err := godotenv.Load(); err != nil { - log.Println("[🟨] No .env file found") - } - - // Init the database - svc.InitDB() - cfg := config.GetConfiguration() - - router := web.SetupRouter() - - srv := &http.Server{ - Addr: cfg.Listen, - Handler: router, - } - - // Run the server in a goroutine - go func() { - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("[🛑] listening failed: %s\n", err) - } - }() - - // Run the hardware service - svc.RunLoggerService() - // Run the cleanup service - svc.RunCleanupService() - - // Setting up signal capturing - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - - // Block until a signal is received - <-quit - log.Println("[🦝] Shutting down server...") - - // Graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("[🛑] Server forced to shutdown:", err) - } - - log.Println("[🦝] Server exiting") -} diff --git a/drive-health/media/design_v1.webp b/drive-health/media/design_v1.webp deleted file mode 100644 index 9f4434a..0000000 Binary files a/drive-health/media/design_v1.webp and /dev/null differ diff --git a/drive-health/media/old-look.png b/drive-health/media/old-look.png deleted file mode 100644 index 192f7b2..0000000 Binary files a/drive-health/media/old-look.png and /dev/null differ diff --git a/drive-health/static/main.js b/drive-health/static/main.js deleted file mode 100644 index 56d815a..0000000 --- a/drive-health/static/main.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @typedef {number} - */ -var initialInputOlder = 0; // miliseconds Unix TimeStamp -/** - * @typedef {number} - */ -var initialInputNewer = 0; // miliseconds Unix TimeStamp - - -/** - * @typedef {HTMLInputElement} - */ -var olderThanInputElement; -/** - * @typedef {HTMLInputElement} - */ -var newerThanInputElement; - -document.addEventListener(`DOMContentLoaded`, initializePage) - -function initializePage() { - - // Update the page's time filter - initialInputOlder = Number(document.getElementById(`inp-older`).textContent.trim()) - initialInputNewer = Number(document.getElementById(`inp-newer`).textContent.trim()) - - // Bind the date elements - olderThanInputElement = document.getElementById(`olderThan`); - newerThanInputElement = document.getElementById(`newerThan`); - - olderThanInputElement.value = convertTimestampToDateTimeLocal(initialInputOlder); - newerThanInputElement.value = convertTimestampToDateTimeLocal(initialInputNewer); - -} - -// Handle one of the date elements having their value changed. -function applyDateInterval() { - const olderTimeStamp = new Date(olderThanInputElement.value).getTime() - const newerTimeStamp = new Date(newerThanInputElement.value).getTime() - - window.location.href = `/?older=${olderTimeStamp}&newer=${newerTimeStamp}`; -} - -/** - * Converts a Unix timestamp to a standard datetime string - * @param {number} timestamp - The Unix timestamp in milliseconds. - * @returns {string} - A normal string with Y-m-d H:i:s format - */ -function convertTimestampToDateTimeLocal(timestamp) { - const date = new Date(timestamp); - const offset = date.getTimezoneOffset() * 60000; // offset in milliseconds - const localDate = new Date(date.getTime() - offset); - return localDate.toISOString().slice(0, 19); -} diff --git a/drive-health/static/style.css b/drive-health/static/style.css deleted file mode 100644 index afae1d9..0000000 --- a/drive-health/static/style.css +++ /dev/null @@ -1,188 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Mono:wght@100..900&family=Roboto:wght@100;300;400&display=swap'); - -:root { - --bg0: #202327; - --bg1: #282d33; - --bg2: #31373f; - --bg3: #3e4248; - --bg4: #1a1c1f; - - --fg0: #bbc0ca; - --fg1: #434c56; - - --acc1: #2aa3f4; - --acc1BG0: #2aa3f450; - --acc1BG1: #2aa3f430; - - --acc2: #2af488; - --acc2BG0: #2af48850; - --acc2BG1: #2af48830; - - --acc3: #f4e02a; - --acc3BG0: #f4e02a50; - --acc3BG1: #f4e02a30; -} - -:root { - color: var(--fg0); - font-family: "Noto Sans Mono", "Roboto", sans-serif; -} - -html, body { - margin: 0; - padding: 0; - - width: 100vw; - - overflow: auto; - - background-color: var(--bg0); -} - -.container { - margin: 1rem auto; - max-width: 768px; - - border-radius: 8px; - border: 1px solid var(--fg1); - - background-color: var(--bg1); - overflow: hidden; -} - -.container-titlebar { - width: 100%; - - background-color: var(--bg2); -} - -.container .pad { - padding: .5rem 1rem; -} - -.container-titlebar h4 { - padding: 0; - margin: 0; -} - -.badge { - font-size: 12px; - font-weight: bold; - padding: .1rem .4rem; - - display: inline-block; - border-radius: 3px; - - color: var(--acc1); - background-color: var(--acc1BG0); - border: 1px solid var(--acc1BG1); -} - -.badge[type="HDD"] { - color: var(--acc2); - background-color: var(--acc2BG0); - border: 1px solid var(--acc2BG1); -} - -.badge[type="NVMe"] { - color: var(--acc3); - background-color: var(--acc3BG0); - border: 1px solid var(--acc3BG1); -} - -.grooved { - background-color: var(--bg0); - border: 1px solid var(--bg1); - - font-size: 12px; - padding: .1rem .3rem; - - border-radius: 4px; -} - - -/* Table */ - -table { - width: 100%; - border-collapse: collapse; -} - -table thead tr { - border-bottom: 1px solid var(--bg2); -} - -.graph-image { - max-width: 100%; -} - -.disk-graph-entry { - background-color: var(--bg3); - - border-radius: 8px; - - padding: .3rem .5rem; -} - - -/* Controls */ - -input { - padding: .25rem .5rem; - font-size: 16px; - background-color: var(--bg3); - color: var(--fg0); - - border: 1px solid var(--fg1); - border-radius: 4px; -} - -.btn { - font-size: 16px; - - padding: .5rem 1rem; - - border: 1px solid var(--fg1); - border-radius: 6px; - - background-color: var(--bg4); - color: var(--fg0); - - cursor: pointer; -} - -.btn:hover { - background-color: var(--bg3); -} - -.input-grp { - display: flex; - flex-flow: column; -} - -.input-grp label { - margin-bottom: .25rem; - font-weight: bold; -} - -.graph-controls { - display: flex; - flex-flow: wrap; - - width: 100%; -} - -.graph-controls input:nth-child(0) { - margin-right: 1rem; -} - -.controls-panel { - display: flex; - flex-flow: column; - - padding: 1rem 0; - - border-bottom: 1px solid var(--fg1); - margin-bottom: 1rem; - -} \ No newline at end of file diff --git a/drive-health/templates/index.html b/drive-health/templates/index.html deleted file mode 100644 index bd9707a..0000000 --- a/drive-health/templates/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - Drive Health Dashboard - - -{{ $older := .older }} -{{ $newer := .newer }} - - -
- -
-
-

Available Disks

-
-
- -
-
- {{ if len .drives }} - - - - - - - - - - - - {{ range .drives }} - {{ $temp := .GetTemperature }} - - - - - - - - {{ if gt $temp 50 }} - - {{ else if gt $temp 30 }} - - {{ else }} - - {{ end }} - - {{ end }} - -
IDNameModelSerialTemperature
#{{ .ID }} {{ .Name }} {{ .Model }} {{ .Serial }}{{ $temp }}°C{{ $temp }}°C{{ $temp }}°C
- {{ else }} -

No hard drives found.

- {{ end }} -
-
- -
- -
- -
-
-

Temperature Graph

-
-
-
-
- - -
-
- - - -
- - -
- -
- - -
- -
- -
- -
-
- - - {{ if len .drives }} - {{ range .drives }} -
-
/dev/{{.Name}}
-

{{.Model}}:{{.HWID}} {{.Size}}

- - {{ .Model }} Image - -
-
- {{ end }} - {{ else }} -

No hard drives found.

- {{ end }} -
-
- -
- - - -