Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe6fcf1ede | |||
| 0aea6b96ca | |||
| afbf1450c2 | |||
| 6a278bc2cf | |||
| 9d1ce790d0 | |||
| fb5d4818b0 | |||
| 3a06920354 | |||
| dd8a6757d1 | |||
| d433a6a54e | |||
| c365988a52 | |||
| 6a1a985306 | |||
| 02996d6288 | |||
| 3d2671650e | |||
| 28658790c8 | |||
| 18f10a9295 | |||
| 67b7a08e4a |
@@ -0,0 +1,25 @@
|
||||
services:
|
||||
app:
|
||||
image: mcr.microsoft.com/devcontainers/base:ubuntu-22.04
|
||||
volumes:
|
||||
- ..:/workspaces/scrutiny:cached
|
||||
command: sleep infinity
|
||||
network_mode: service:influxdb
|
||||
|
||||
influxdb:
|
||||
image: influxdb:2.8
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8086:8086"
|
||||
environment:
|
||||
- DOCKER_INFLUXDB_INIT_MODE=setup
|
||||
- DOCKER_INFLUXDB_INIT_USERNAME=admin
|
||||
- DOCKER_INFLUXDB_INIT_PASSWORD=password12345
|
||||
- DOCKER_INFLUXDB_INIT_ORG=scrutiny
|
||||
- DOCKER_INFLUXDB_INIT_BUCKET=metrics
|
||||
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-auth-token
|
||||
volumes:
|
||||
- scrutiny-influxdb-data:/var/lib/influxdb2
|
||||
|
||||
volumes:
|
||||
scrutiny-influxdb-data:
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "Scrutiny Dev (rootless docker)",
|
||||
"dockerComposeFile": "../docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/scrutiny",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/go:1": "1.25",
|
||||
"ghcr.io/devcontainers/features/node:1": "lts"
|
||||
},
|
||||
|
||||
"onCreateCommand": "sudo apt-get update && sudo apt-get install -y smartmontools iputils-ping chromium-browser",
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"forwardPorts": [8080, 8086],
|
||||
|
||||
"postCreateCommand": "bash .devcontainer/setup.sh",
|
||||
"remoteUser": "root",
|
||||
"containerUser": "root",
|
||||
"updateRemoteUserUID": false
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Scrutiny Dev (docker)",
|
||||
"dockerComposeFile": "../docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/scrutiny",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/go:1": "1.25",
|
||||
"ghcr.io/devcontainers/features/node:1": "lts"
|
||||
},
|
||||
|
||||
"onCreateCommand": "sudo apt-get update && sudo apt-get install -y smartmontools iputils-ping chromium-browser",
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"forwardPorts": [8080, 8086],
|
||||
|
||||
"postCreateCommand": "bash .devcontainer/setup.sh",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "Scrutiny Dev (podman)",
|
||||
"dockerComposeFile": "../docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/scrutiny",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/go:1": "1.25",
|
||||
"ghcr.io/devcontainers/features/node:1": "lts"
|
||||
},
|
||||
|
||||
"onCreateCommand": "sudo apt-get update && sudo apt-get install -y smartmontools iputils-ping chromium-browser",
|
||||
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"forwardPorts": [8080, 8086],
|
||||
|
||||
"postCreateCommand": "bash .devcontainer/setup.sh",
|
||||
"remoteEnv": {
|
||||
"PODMAN_USERNS": "keep-id"
|
||||
},
|
||||
"containerUser": "vscode",
|
||||
"updateRemoteUserUID": true
|
||||
}
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Starting Scrutiny Setup..."
|
||||
|
||||
if [ ! -f "scrutiny.yaml" ]; then
|
||||
echo "Creating scrutiny.yaml from template..."
|
||||
cat <<EOF > scrutiny.yaml
|
||||
version: 1
|
||||
web:
|
||||
listen:
|
||||
port: 8080
|
||||
host: 0.0.0.0
|
||||
database:
|
||||
location: ./scrutiny.db
|
||||
src:
|
||||
frontend:
|
||||
path: ./dist
|
||||
influxdb:
|
||||
retention_policy: false
|
||||
token: "my-super-secret-auth-token"
|
||||
org: "scrutiny"
|
||||
bucket: "metrics"
|
||||
host: "localhost"
|
||||
port: 8086
|
||||
log:
|
||||
file: 'web.log'
|
||||
level: DEBUG
|
||||
EOF
|
||||
else
|
||||
echo "scrutiny.yaml already exists."
|
||||
fi
|
||||
|
||||
echo "Vendoring Go modules..."
|
||||
go mod vendor
|
||||
|
||||
echo "Installing Node modules..."
|
||||
cd webapp/frontend
|
||||
npm install
|
||||
|
||||
echo "Setup Complete! Ready to code."
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
# Service containers to run with `build` (Required for end-to-end testing)
|
||||
services:
|
||||
influxdb:
|
||||
image: influxdb:2.2
|
||||
image: influxdb:2.8
|
||||
env:
|
||||
DOCKER_INFLUXDB_INIT_MODE: setup
|
||||
DOCKER_INFLUXDB_INIT_USERNAME: admin
|
||||
@@ -129,3 +129,45 @@ jobs:
|
||||
path: |
|
||||
scrutiny-web-*
|
||||
scrutiny-collector-metrics-*
|
||||
|
||||
build-docker:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build omnibus
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
file: docker/Dockerfile
|
||||
push: false
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Build collector
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
context: .
|
||||
file: docker/Dockerfile.collector
|
||||
push: false
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Build web
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
context: .
|
||||
file: docker/Dockerfile.web
|
||||
push: false
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
# Service containers to run with `build` (Required for end-to-end testing)
|
||||
services:
|
||||
influxdb:
|
||||
image: influxdb:2.2
|
||||
image: influxdb:2.8
|
||||
env:
|
||||
DOCKER_INFLUXDB_INIT_MODE: setup
|
||||
DOCKER_INFLUXDB_INIT_USERNAME: admin
|
||||
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Scrutiny",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/webapp/backend/cmd/scrutiny/scrutiny.go",
|
||||
"args": ["start", "--config", "./scrutiny.yaml"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"DEBUG": "true"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"preLaunchTask": "Build Frontend",
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "Listening and serving HTTP on",
|
||||
"uriFormat": "http://localhost:8080/web/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Run Collector",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/collector/cmd/collector-metrics/collector-metrics.go",
|
||||
"args": ["run", "--debug"],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"COLLECTOR_DEBUG": "true"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build Frontend",
|
||||
"type": "shell",
|
||||
"command": "cd webapp/frontend && npm run build:prod -- --output-path=../../dist"
|
||||
}
|
||||
]
|
||||
}
|
||||
+9
-4
@@ -147,6 +147,11 @@ The Scrutiny repository is a [monorepo](https://en.wikipedia.org/wiki/Monorepo)
|
||||
|
||||
Depending on the functionality you are adding, you may need to setup a development environment for 1 or more projects.
|
||||
|
||||
# Devcontainer
|
||||
Devcontainer configurations are available to build and run Scrutiny (WebUI and Collector) in a fully isolated environment.
|
||||
When opening the project with vscode, choose "Reopen in Container". Three configurations are available depending on your
|
||||
container runtime and setup: docker, docker-rootless, and podman.
|
||||
|
||||
# Modifying the Scrutiny Backend Server (API)
|
||||
|
||||
1. install the [Go runtime](https://go.dev/doc/install) (v1.25)
|
||||
@@ -177,7 +182,7 @@ Depending on the functionality you are adding, you may need to setup a developme
|
||||
```
|
||||
4. start a InfluxDB docker container.
|
||||
```bash
|
||||
docker run -p 8086:8086 --rm influxdb:2.2
|
||||
docker run -p 8086:8086 --rm influxdb:2.8
|
||||
```
|
||||
5. start the scrutiny web server
|
||||
```bash
|
||||
@@ -230,7 +235,7 @@ you'll need to follow the steps below:
|
||||
```
|
||||
4. start a InfluxDB docker container.
|
||||
```bash
|
||||
docker run -p 8086:8086 --rm influxdb:2.2
|
||||
docker run -p 8086:8086 --rm influxdb:2.8
|
||||
```
|
||||
5. build the Angular Frontend Application
|
||||
```bash
|
||||
@@ -254,7 +259,7 @@ If you'd like to populate the database with some test data, you can run the fol
|
||||
> This is done automatically by the `webapp/backend/pkg/models/testdata/helper.go` script
|
||||
|
||||
```
|
||||
docker run -p 8086:8086 --rm influxdb:2.2
|
||||
docker run -p 8086:8086 --rm influxdb:2.8
|
||||
|
||||
|
||||
# curl -X POST -H "Content-Type: application/json" -d @webapp/backend/pkg/web/testdata/register-devices-req.json localhost:8080/api/devices/register
|
||||
@@ -322,7 +327,7 @@ docker run -p 8086:8086 -d --rm \
|
||||
-e DOCKER_INFLUXDB_INIT_ORG=scrutiny \
|
||||
-e DOCKER_INFLUXDB_INIT_BUCKET=metrics \
|
||||
-e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-auth-token \
|
||||
influxdb:2.2
|
||||
influxdb:2.8
|
||||
go test ./...
|
||||
|
||||
```
|
||||
|
||||
@@ -121,19 +121,23 @@ binary-frontend-test-coverage:
|
||||
########################################################################################################################
|
||||
# Docker
|
||||
# NOTE: these docker make targets are only used for local development (not used by Github Actions/CI)
|
||||
# NOTE: docker-web and docker-omnibus require `make binary-frontend` or frontend.tar.gz content in /dist before executing.
|
||||
########################################################################################################################
|
||||
.PHONY: docker-smartmontools
|
||||
docker-smartmontools:
|
||||
@echo "building smartmontools docker image"
|
||||
docker build $(DOCKER_TARGETARCH_BUILD_ARG) -f docker/Dockerfile.smartmontools -t smartmontools-build .
|
||||
|
||||
.PHONY: docker-collector
|
||||
docker-collector:
|
||||
docker-collector: docker-smartmontools
|
||||
@echo "building collector docker image"
|
||||
docker build $(DOCKER_TARGETARCH_BUILD_ARG) -f docker/Dockerfile.collector -t analogj/scrutiny-dev:collector .
|
||||
docker build $(DOCKER_TARGETARCH_BUILD_ARG) -f docker/Dockerfile.collector -t ghcr.io/analogj/scrutiny-dev:collector .
|
||||
|
||||
.PHONY: docker-web
|
||||
docker-web:
|
||||
@echo "building web docker image"
|
||||
docker build $(DOCKER_TARGETARCH_BUILD_ARG) -f docker/Dockerfile.web -t analogj/scrutiny-dev:web .
|
||||
docker build $(DOCKER_TARGETARCH_BUILD_ARG) -f docker/Dockerfile.web -t ghcr.io/analogj/scrutiny-dev:web .
|
||||
|
||||
.PHONY: docker-omnibus
|
||||
docker-omnibus:
|
||||
docker-omnibus: docker-smartmontools
|
||||
@echo "building omnibus docker image"
|
||||
docker build $(DOCKER_TARGETARCH_BUILD_ARG) -f docker/Dockerfile -t analogj/scrutiny-dev:omnibus .
|
||||
docker build $(DOCKER_TARGETARCH_BUILD_ARG) -f docker/Dockerfile -t ghcr.io/analogj/scrutiny-dev:omnibus .
|
||||
|
||||
@@ -102,7 +102,7 @@ other Docker images:
|
||||
- `ghcr.io/analogj/scrutiny:latest-collector` - Contains the Scrutiny data collector, `smartctl` binary and cron-like
|
||||
scheduler. You can run one collector on each server.
|
||||
- `ghcr.io/analogj/scrutiny:latest-web` - Contains the Web UI and API. Only one container necessary
|
||||
- `influxdb:2.2` - InfluxDB image, used by the Web container to persist SMART data. Only one container necessary
|
||||
- `influxdb:2.8` - InfluxDB image, used by the Web container to persist SMART data. Only one container necessary
|
||||
See [docs/TROUBLESHOOTING_INFLUXDB.md](./docs/TROUBLESHOOTING_INFLUXDB.md)
|
||||
|
||||
> See [docker/example.hubspoke.docker-compose.yml](https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml) for a docker-compose file.
|
||||
@@ -111,7 +111,7 @@ other Docker images:
|
||||
docker run -p 8086:8086 --restart unless-stopped \
|
||||
-v `pwd`/influxdb2:/var/lib/influxdb2 \
|
||||
--name scrutiny-influxdb \
|
||||
influxdb:2.2
|
||||
influxdb:2.8
|
||||
|
||||
docker run -p 8080:8080 --restart unless-stopped \
|
||||
-v `pwd`/scrutiny:/opt/scrutiny/config \
|
||||
@@ -128,6 +128,10 @@ docker run --restart unless-stopped \
|
||||
ghcr.io/analogj/scrutiny:latest-collector
|
||||
```
|
||||
|
||||
### Hub rootless installation using Podman Quadlets
|
||||
|
||||
See [docs/INSTALL_ROOTLESS_PODMAN.md](docs/INSTALL_ROOTLESS_PODMAN.md) for instructions.
|
||||
|
||||
## Manual Installation (without-Docker)
|
||||
|
||||
While the easiest way to get started with [Scrutiny is using Docker](https://github.com/AnalogJ/scrutiny#docker),
|
||||
|
||||
+22
-3
@@ -12,7 +12,7 @@ RUN make binary-frontend
|
||||
|
||||
|
||||
######## Build the backend
|
||||
FROM golang:1.25-bookworm as backendbuild
|
||||
FROM golang:1.25-trixie as backendbuild
|
||||
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
COPY --link . /go/src/github.com/analogj/scrutiny
|
||||
@@ -23,8 +23,25 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
|
||||
RUN make binary-clean binary-all WEB_BINARY_NAME=scrutiny
|
||||
|
||||
|
||||
######## Build smartmontools from source
|
||||
FROM debian:trixie-slim AS smartmontoolsbuild
|
||||
ARG SMARTMONTOOLS_VER=7.5
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl gcc g++ gnupg make \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -L "https://github.com/smartmontools/smartmontools/releases/download/RELEASE_$(echo ${SMARTMONTOOLS_VER} | tr '.' '_')/smartmontools-${SMARTMONTOOLS_VER}.tar.gz" -o /tmp/smartmontools.tar.gz \
|
||||
&& tar -xzf /tmp/smartmontools.tar.gz -C /tmp \
|
||||
&& cd /tmp/smartmontools-${SMARTMONTOOLS_VER} \
|
||||
&& ./configure --prefix=/usr LDFLAGS='-static' --without-libcap-ng --without-libsystemd \
|
||||
&& make -j"$(nproc)" \
|
||||
&& make install \
|
||||
&& /usr/sbin/update-smart-drivedb \
|
||||
&& rm -rf /tmp/smartmontools*
|
||||
|
||||
|
||||
######## Combine build artifacts in runtime image
|
||||
FROM debian:bookworm-slim as runtime
|
||||
FROM debian:trixie-slim AS runtime
|
||||
ARG TARGETARCH
|
||||
EXPOSE 8080
|
||||
WORKDIR /opt/scrutiny
|
||||
@@ -40,7 +57,6 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
|
||||
ca-certificates \
|
||||
cron \
|
||||
curl \
|
||||
smartmontools \
|
||||
tzdata \
|
||||
procps \
|
||||
xz-utils \
|
||||
@@ -62,6 +78,9 @@ RUN curl -L https://dl.influxdata.com/influxdb/releases/influxdb2-${INFLUXVER}-$
|
||||
|
||||
COPY /rootfs /
|
||||
|
||||
COPY --from=smartmontoolsbuild /usr/sbin/smartctl /usr/sbin/smartctl
|
||||
COPY --from=smartmontoolsbuild /usr/share/smartmontools/ /usr/share/smartmontools/
|
||||
|
||||
COPY --link --from=backendbuild --chmod=755 /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
||||
COPY --link --from=backendbuild --chmod=755 /go/src/github.com/analogj/scrutiny/scrutiny-collector-metrics /opt/scrutiny/bin/
|
||||
COPY --link --from=frontendbuild --chmod=644 /go/src/github.com/analogj/scrutiny/dist /opt/scrutiny/web
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
|
||||
########
|
||||
FROM golang:1.25-bookworm as backendbuild
|
||||
FROM golang:1.25-trixie AS backendbuild
|
||||
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
|
||||
@@ -13,12 +13,31 @@ COPY . /go/src/github.com/analogj/scrutiny
|
||||
RUN apt-get update && apt-get install -y file && rm -rf /var/lib/apt/lists/*
|
||||
RUN make binary-clean binary-collector
|
||||
|
||||
######## Build smartmontools from source
|
||||
FROM debian:trixie-slim AS smartmontoolsbuild
|
||||
ARG SMARTMONTOOLS_VER=7.5
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl gcc g++ gnupg make \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -L "https://github.com/smartmontools/smartmontools/releases/download/RELEASE_$(echo ${SMARTMONTOOLS_VER} | tr '.' '_')/smartmontools-${SMARTMONTOOLS_VER}.tar.gz" -o /tmp/smartmontools.tar.gz \
|
||||
&& tar -xzf /tmp/smartmontools.tar.gz -C /tmp \
|
||||
&& cd /tmp/smartmontools-${SMARTMONTOOLS_VER} \
|
||||
&& ./configure --prefix=/usr LDFLAGS='-static' --without-libcap-ng --without-libsystemd \
|
||||
&& make -j"$(nproc)" \
|
||||
&& make install \
|
||||
&& /usr/sbin/update-smart-drivedb \
|
||||
&& rm -rf /tmp/smartmontools*
|
||||
|
||||
########
|
||||
FROM debian:bookworm-slim as runtime
|
||||
FROM debian:trixie-slim AS runtime
|
||||
WORKDIR /opt/scrutiny
|
||||
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
||||
|
||||
RUN apt-get update && apt-get install -y cron smartmontools ca-certificates tzdata && rm -rf /var/lib/apt/lists/* && update-ca-certificates
|
||||
RUN apt-get update && apt-get install -y cron ca-certificates tzdata && rm -rf /var/lib/apt/lists/* && update-ca-certificates
|
||||
|
||||
COPY --from=smartmontoolsbuild /usr/sbin/smartctl /usr/sbin/smartctl
|
||||
COPY --from=smartmontoolsbuild /usr/share/smartmontools/ /usr/share/smartmontools/
|
||||
|
||||
COPY /docker/entrypoint-collector.sh /entrypoint-collector.sh
|
||||
COPY /rootfs/etc/cron.d/scrutiny /etc/cron.d/scrutiny
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
########################################################################################################################
|
||||
# Smartmontools Builder
|
||||
# - Builds smartctl from source as a static binary.
|
||||
# - Updates the drive database to include the latest drive models since it can change between releases.
|
||||
# - Used as a shared build stage by Dockerfile and Dockerfile.collector.
|
||||
########################################################################################################################
|
||||
FROM debian:trixie-slim
|
||||
ARG SMARTMONTOOLS_VER=7.5
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl gcc g++ gnupg make \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -L "https://github.com/smartmontools/smartmontools/releases/download/RELEASE_$(echo ${SMARTMONTOOLS_VER} | tr '.' '_')/smartmontools-${SMARTMONTOOLS_VER}.tar.gz" -o /tmp/smartmontools.tar.gz \
|
||||
&& tar -xzf /tmp/smartmontools.tar.gz -C /tmp \
|
||||
&& cd /tmp/smartmontools-${SMARTMONTOOLS_VER} \
|
||||
&& ./configure --prefix=/usr LDFLAGS='-static' --without-libcap-ng --without-libsystemd \
|
||||
&& make -j"$(nproc)" \
|
||||
&& make install \
|
||||
&& /usr/sbin/update-smart-drivedb \
|
||||
&& rm -rf /tmp/smartmontools*
|
||||
@@ -11,7 +11,7 @@ COPY --link . /go/src/github.com/analogj/scrutiny
|
||||
RUN make binary-frontend
|
||||
|
||||
######## Build the backend
|
||||
FROM golang:1.25-bookworm as backendbuild
|
||||
FROM golang:1.25-trixie as backendbuild
|
||||
|
||||
WORKDIR /go/src/github.com/analogj/scrutiny
|
||||
COPY --link . /go/src/github.com/analogj/scrutiny
|
||||
@@ -21,7 +21,7 @@ RUN make binary-clean binary-all WEB_BINARY_NAME=scrutiny
|
||||
|
||||
|
||||
######## Combine build artifacts in runtime image
|
||||
FROM debian:bookworm-slim as runtime
|
||||
FROM debian:trixie-slim as runtime
|
||||
EXPOSE 8080
|
||||
WORKDIR /opt/scrutiny
|
||||
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
||||
|
||||
@@ -3,7 +3,7 @@ version: '2.4'
|
||||
services:
|
||||
influxdb:
|
||||
restart: unless-stopped
|
||||
image: influxdb:2.2
|
||||
image: influxdb:2.8
|
||||
ports:
|
||||
- '8086:8086'
|
||||
volumes:
|
||||
|
||||
+11
-13
@@ -49,19 +49,15 @@ contains the connection and notification details but I always find it easier to
|
||||
docker-compose.
|
||||
|
||||
```yaml
|
||||
version: "3.4"
|
||||
|
||||
networks:
|
||||
monitoring: # A common network for all monitoring services to communicate into
|
||||
external: true
|
||||
notifications: # To Gotify or another Notification service
|
||||
external: true
|
||||
|
||||
services:
|
||||
influxdb:
|
||||
restart: unless-stopped
|
||||
container_name: influxdb
|
||||
image: influxdb:2.1-alpine
|
||||
image: influxdb:2.8
|
||||
ports:
|
||||
- 8086:8086
|
||||
volumes:
|
||||
@@ -73,7 +69,8 @@ services:
|
||||
- DOCKER_INFLUXDB_INIT_PASSWORD=${PASSWORD}
|
||||
- DOCKER_INFLUXDB_INIT_ORG=homelab
|
||||
- DOCKER_INFLUXDB_INIT_BUCKET=scrutiny
|
||||
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=your-very-secret-token
|
||||
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=SUPER-SECRET-TOKEN
|
||||
- TZ=Europe/Stockholm
|
||||
networks:
|
||||
- monitoring
|
||||
|
||||
@@ -85,17 +82,20 @@ services:
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ${DIR_CONFIG}/scrutiny/config:/opt/scrutiny/config
|
||||
- ${DIR_CONFIG}/config:/opt/scrutiny/config
|
||||
environment:
|
||||
- SCRUTINY_WEB_INFLUXDB_HOST=influxdb
|
||||
- SCRUTINY_WEB_INFLUXDB_PORT=8086
|
||||
- SCRUTINY_WEB_INFLUXDB_TOKEN=your-very-secret-token
|
||||
- SCRUTINY_WEB_INFLUXDB_TOKEN=SUPER-SECRET-TOKEN
|
||||
- SCRUTINY_WEB_INFLUXDB_ORG=homelab
|
||||
- SCRUTINY_WEB_INFLUXDB_BUCKET=scrutiny
|
||||
# Optional but highly recommended to notify you in case of a problem
|
||||
- SCRUTINY_NOTIFY_URLS=["http://gotify:80/message?token=a-gotify-token"]
|
||||
# Optional but highly recommended to notify you in case of a problem; space-separated list of shoutrrr uri's
|
||||
# https://github.com/AnalogJ/scrutiny/blob/master/docs/TROUBLESHOOTING_NOTIFICATIONS.md
|
||||
- SCRUTINY_NOTIFY_URLS=http://gotify:80/message?token=a-gotify-token ntfy://username:password@host:port/topic
|
||||
- TZ=Europe/Stockholm
|
||||
depends_on:
|
||||
- influxdb
|
||||
influxdb:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- notifications
|
||||
- monitoring
|
||||
@@ -164,8 +164,6 @@ Also all drives that you wish to monitor need to be presented to the container u
|
||||
The image handles the periodic scanning of the drives.
|
||||
|
||||
```yaml
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
|
||||
collector:
|
||||
|
||||
+315
-13
@@ -10,9 +10,9 @@ Scrutiny is made up of three components: an influxdb Database, a collector and a
|
||||
|
||||
## InfluxDB
|
||||
|
||||
Please follow the official InfluxDB installation guide. Note, you'll need to install v2.2.0+.
|
||||
Please follow the official InfluxDB installation guide. Note, you'll need to install v2.8.0+.
|
||||
|
||||
https://docs.influxdata.com/influxdb/v2.2/install/
|
||||
https://docs.influxdata.com/influxdb/v2/install/
|
||||
|
||||
## Webapp/API
|
||||
|
||||
@@ -122,6 +122,11 @@ So you'll need to install the v7+ version using one of the following commands:
|
||||
- `dnf install smartmontools`
|
||||
- **FreeBSD:** `pkg install smartmontools`
|
||||
|
||||
The following additional dependencies are needed if you want to run the collector as an unprivileged user:
|
||||
|
||||
- systemd version > 235
|
||||
- a restricted user account
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Now let's create a directory structure to contain the Scrutiny collector binary.
|
||||
@@ -133,40 +138,337 @@ mkdir -p /opt/scrutiny/bin
|
||||
|
||||
### Download Files
|
||||
|
||||
Next, we'll download the Scrutiny collector binary from the [latest Github release](https://github.com/analogj/scrutiny/releases).
|
||||
The file you need to download is named:
|
||||
Next, we'll download the Scrutiny collector binary from the [latest Github release](https://github.com/analogj/scrutiny/releases). You are looking for the one titled **scrutiny-collector-metrics-linux-amd64** unless you know you are on arm.
|
||||
|
||||
- **scrutiny-collector-metrics-linux-amd64** - save this file to `/opt/scrutiny/bin`
|
||||
```sh
|
||||
wget -O /tmp/scrutiny-collector-metrics https://github.com/AnalogJ/scrutiny/releases/latest/download/scrutiny-collector-metrics-linux-amd64
|
||||
```
|
||||
|
||||
Optional, but recommended: Before continuing it's recommended you compare the sha from the release page with the downloaded file to ensure it's the same file and not corrupted/tampered with. The command to do this is:
|
||||
|
||||
`echo "SHA_GOES_HERE /tmp/scrutiny-collector-metrics" | sha256sum -c`
|
||||
|
||||
example for the v0.8.6 release:
|
||||
|
||||
`echo "4c163645ce24e5487f4684a25ec73485d77a82a57f084808ff5aad0c11499ad2 /tmp/scrutiny-collector-metrics" | sha256sum -c`
|
||||
|
||||
followed by:
|
||||
|
||||
`sudo mv /tmp/scrutiny-collector-metrics /opt/scrutiny/bin/`
|
||||
|
||||
to move the binary to its final resting place
|
||||
|
||||
|
||||
### Prepare Scrutiny
|
||||
|
||||
Now that we have downloaded the required files, let's prepare the filesystem.
|
||||
|
||||
```
|
||||
```sh
|
||||
# Let's make sure the Scrutiny collector is executable.
|
||||
chmod +x /opt/scrutiny/bin/scrutiny-collector-metrics-linux-amd64
|
||||
chmod +x /opt/scrutiny/bin/scrutiny-collector-metrics
|
||||
```
|
||||
|
||||
if you are using SELinux, you may need to also do the following:
|
||||
|
||||
```sh
|
||||
# tell SELinux to allow these binaries
|
||||
sudo semanage fcontext -a -t bin_t "/opt/scrutiny/bin(/.*)?"
|
||||
# update labels
|
||||
sudo restorecon -Rv /opt/scrutiny/bin
|
||||
```
|
||||
|
||||
|
||||
### Start Scrutiny Collector, Populate Webapp
|
||||
|
||||
Next, we will manually trigger the collector, to populate the Scrutiny dashboard:
|
||||
|
||||
> NOTE: if you need to pass a config file to the scrutiny collector, you can provide it using the `--config` flag.
|
||||
|
||||
```
|
||||
/opt/scrutiny/bin/scrutiny-collector-metrics-linux-amd64 run --api-endpoint "http://localhost:8080"
|
||||
```sh
|
||||
/opt/scrutiny/bin/scrutiny-collector-metrics run --api-endpoint "http://localhost:8080"
|
||||
```
|
||||
|
||||
### Schedule Collector with Cron
|
||||
### Schedule Collector with (root) Cron
|
||||
|
||||
Finally you need to schedule the collector to run periodically.
|
||||
This may be different depending on your OS/environment, but it may look something like this:
|
||||
|
||||
```
|
||||
```sh
|
||||
# open crontab
|
||||
crontab -e
|
||||
sudo crontab -e
|
||||
|
||||
# add a line for Scrutiny
|
||||
*/15 * * * * . /etc/profile; /opt/scrutiny/bin/scrutiny-collector-metrics-linux-amd64 run --api-endpoint "http://localhost:8080"
|
||||
*/15 * * * * . /etc/profile; /opt/scrutiny/bin/scrutiny-collector-metrics run --api-endpoint "http://localhost:8080"
|
||||
```
|
||||
|
||||
### Schedule Collector with Systemd (rootless)
|
||||
|
||||
Alternatively you can run `scrutiny-collector-metrics` as non-root so long as the relevant capabilities and permissions are granted.
|
||||
|
||||
|
||||
#### Creating a Restricted Service Account
|
||||
|
||||
This is the account that will run `scrutiny-collector-metrics`. Note this isn't strictly needed for all setups, but is useful from a logging/auditing perspective.
|
||||
|
||||
- Debian-based distros:
|
||||
- `sudo adduser --system scrutiny-svc --group --home /opt/scrutiny-svc`
|
||||
- RHEL-based distros:
|
||||
- `sudo useradd --system --home-dir /opt/scrutiny-svc --shell /sbin/nologin scrutiny-svc`
|
||||
|
||||
Next, add the user to the `disk` group:
|
||||
|
||||
```sh
|
||||
sudo usermod -aG disk scrutiny-svc
|
||||
```
|
||||
|
||||
|
||||
#### Creating a Restricted Systemd Service using AmbientCapabilities (easier)
|
||||
|
||||
This is the simpler setup, which allows you to run scrutiny rootless, but depending on what you want, may require granting more permissions to scrutiny than you would like to.
|
||||
|
||||
1. go to `/etc/systemd/system`
|
||||
2. create scrutiny-collector.service with the following contents:
|
||||
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Daily Restricted Scrutiny Collector
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
[Unit]
|
||||
Description=Daily Restricted Scrutiny Collector
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=scrutiny-svc
|
||||
Group=disk
|
||||
ExecStart=/opt/scrutiny/bin/scrutiny-collector-metrics run --api-endpoint "http://localhost:8080"
|
||||
|
||||
# --- PRIVILEGE LOCKDOWN ---
|
||||
## CAP_SYS_RAWIO is needed for SATA drives
|
||||
AmbientCapabilities=CAP_SYS_RAWIO
|
||||
CapabilityBoundingSet=CAP_SYS_RAWIO
|
||||
## unfortunately nvme drives require CAP_SYS_ADMIN
|
||||
## if you want nvme drives you must do the following:
|
||||
#AmbientCapabilities=CAP_SYS_RAWIO CAP_SYS_ADMIN
|
||||
#CapabilityBoundingSet=
|
||||
|
||||
NoNewPrivileges=yes
|
||||
|
||||
# Security/sandboxing settings
|
||||
KeyringMode=private
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
PrivateDevices=no
|
||||
## you can restrict devices using:
|
||||
#DevicePolicy=closed
|
||||
#DeviceAllow=/dev/sda r
|
||||
#DeviceAllow=/dev/nvme0 r
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectClock=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
RemoveIPC=yes
|
||||
RestrictSUIDSGID=true
|
||||
|
||||
|
||||
# --- NETWORK LOCKDOWN
|
||||
## use these to restrict what scrutiny can talk to over the network
|
||||
## if using a hub on a different host you will need to change the values accordingly
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
```
|
||||
|
||||
Additionally, for nvme drives you may need to create a udev rule on many systems, as /dev/nvme* is often owned only by root:
|
||||
|
||||
##### add udev rule `/etc/udev/rules.d/99-nvme.rules` with contents:
|
||||
|
||||
```
|
||||
KERNEL=="nvme[0-9]*", GROUP="disk", MODE="0640"
|
||||
```
|
||||
|
||||
then run the following commands to load the udev rule:
|
||||
|
||||
```sh
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --subsystem-match=nvme --action=add
|
||||
```
|
||||
|
||||
|
||||
##### Pros:
|
||||
|
||||
- easy to maintain
|
||||
- much better than running as root (especially if you don't need nvme drives)
|
||||
- there are no privilege escalations needed
|
||||
|
||||
|
||||
##### Cons:
|
||||
|
||||
NOTE: These cons basically only apply if a major supply-chain attack happens against scrutiny, and reflect a worst-case scenario that is unlikely to ever occur:
|
||||
|
||||
- CAP_SYS_RAWIO allows for data exfiltration/modification from SATA drives (ssh keys, /etc/shadow, etc)
|
||||
- CAP_SYS_ADMIN would theoretically allow for significant system compromise
|
||||
- nvme drives requires a udev rule for reliable access
|
||||
|
||||
|
||||
If you are happy with that, you can jump to [Create a Systemd Timer to run scrutiny-collector.service](#create-a-systemd-timer-to-run-scrutiny-collectorservice)
|
||||
|
||||
|
||||
#### Creating a Restricted Systemd Service using sudo and Shim Script
|
||||
|
||||
If granting scrutiny `CAP_SYS_RAWIO` and/or `CAP_SYS_ADMIN` exceeds your risk appetite, you have another option, though one more complicated and with its own set of pros/cons
|
||||
|
||||
1. run `sudo mkdir -p /opt/smartctl-shim/bin`
|
||||
2. edit `/opt/smartctl-shim/bin/smartctl` with the following content:
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
# Shim for accounts to use smartctl without being root
|
||||
# for automation requires the account be in sudoers
|
||||
exec /usr/bin/sudo /usr/sbin/smartctl "$@"
|
||||
```
|
||||
|
||||
3. create a new `scrutiny-collector` file in `/etc/sudoers.d/`
|
||||
4. inside `/etc/sudoers.d/scrutiny-collector` add the following:
|
||||
|
||||
```sh
|
||||
scrutiny-svc ALL=(root) NOPASSWD: /usr/sbin/smartctl *
|
||||
```
|
||||
|
||||
5. go to `/etc/systemd/system`
|
||||
6. create scrutiny-collector.service with the following contents:
|
||||
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Daily Restricted Scrutiny Collector
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=scrutiny-svc
|
||||
Environment="PATH=/opt/smartctl-shim/bin:/usr/bin:/bin"
|
||||
ExecStart=/opt/scrutiny/bin/scrutiny-collector-metrics run --api-endpoint "http://localhost:8080"
|
||||
|
||||
# --- PRIVILEGE LOCKDOWN ---
|
||||
## we use sudo to elevate privileges for smartctl only, so no Ambient Capabilities are needed
|
||||
AmbientCapabilities=
|
||||
## CAP_SYS_RAWIO is needed for SATA drives
|
||||
CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_AUDIT_WRITE CAP_SYS_RAWIO CAP_SYS_RESOURCE
|
||||
## unfortunately nvme drives require CAP_SYS_ADMIN
|
||||
## if you want nvme drives you must do the following:
|
||||
# CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_AUDIT_WRITE CAP_SYS_RAWIO CAP_SYS_ADMIN CAP_SYS_RESOURCE
|
||||
|
||||
## since sudo needs to be used to elevate permissions in this setup, we need to allow new privileges
|
||||
NoNewPrivileges=no
|
||||
|
||||
# Security/sandboxing settings
|
||||
KeyringMode=private
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
PrivateDevices=no
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectClock=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
RemoveIPC=yes
|
||||
RestrictSUIDSGID=true
|
||||
|
||||
|
||||
# --- NETWORK LOCKDOWN
|
||||
## use these to restrict what scrutiny can talk to over the network
|
||||
## if using a hub on a different host you will need to change the values accordingly
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
IPAddressDeny=any
|
||||
IPAddressAllow=localhost
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
|
||||
##### Pros:
|
||||
|
||||
- the scrutiny binary itself will not have permissions like CAP_SYS_ADMIN
|
||||
- much better than running as root (especially if you don't need nvme drives)
|
||||
- `sudo` restricts privilege escalation to just `smartctl`
|
||||
- no udev rule needed
|
||||
|
||||
|
||||
##### Cons:
|
||||
|
||||
NOTE: These cons basically only apply if a major supply-chain attack happens against scrutiny, and reflect a worst-case scenario that is unlikely to ever occur:
|
||||
|
||||
- Any sort of privilege escalation attack in sudo could theoretically allow a compromised scrutiny to gain additional privileges, since the process has permission to escelate privileges in general
|
||||
- Even though sudo only allows `smartctl`, it still has `CAP_SYS_RAWIO` and `CAP_SYS_ADMIN` so in theory the same attacks from the first method are possible, though now only with an exploit using smartctl instead of scrutiny directly
|
||||
- even though you don't need a udev rule, this adds a lot of additional administrative overhead
|
||||
- while the scrutiny binary itself isn't elevated, it has a sub-process that is running as root (systemctl)
|
||||
|
||||
#### Create a Systemd Timer to run scrutiny-collector.service
|
||||
|
||||
First, lets test our service. It doesn't matter which method you used above, as either way you need to load and run it.
|
||||
|
||||
```sh
|
||||
# reload changes for systemd services
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# enable the service
|
||||
sudo systemctl enable scrutiny-collector.service
|
||||
|
||||
# now run the service
|
||||
sudo systemctl start scrutiny-collector.service
|
||||
```
|
||||
|
||||
You should see the data in your hub instance of scrutiny now. If your run into issues I recommend turning on debug logging for scrutiny and checking your system logs using journalctl. It may be a permission is missing or wrong.
|
||||
|
||||
Now that things have been validated, lets create the systemd timer to run the service for us on a schedule:
|
||||
|
||||
1. if you are not still there, go to `/etc/systemd/system`
|
||||
2. create scrutiny-collector.timer with the following contents:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=Run Scruitiny Collector daily at 2am
|
||||
|
||||
[Timer]
|
||||
# Standard calendar trigger
|
||||
OnCalendar=*-*-* 02:00:00
|
||||
# Ensures the job runs if the computer was off at 2am
|
||||
Persistent=true
|
||||
# Minimizes I/O spikes by staggering start time
|
||||
RandomizedDelaySec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
```
|
||||
|
||||
Update the schedule as you see fit for your needs
|
||||
|
||||
Once you are satisfied with our timer, you'll need to load and enable it:
|
||||
|
||||
```sh
|
||||
# reload changes for systemd services
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# now enable the timer
|
||||
sudo systemctl enable --now scrutiny-collector.timer
|
||||
```
|
||||
|
||||
That's it! you're done. You can check the status of the timer using `sudo systemctl status scrutiny-collector.timer
|
||||
`
|
||||
@@ -0,0 +1,170 @@
|
||||
# Rootless Podman Quadlet Install
|
||||
|
||||
Note: These instructions are written with Podman 4.9 in mind, as that's what's available on Ubuntu 24.04. Podman 5+ can simplify the process using a .pod file to run both the hub and influxdb instance in the same pod, sharing localhost. This is a fairly trivial change should anyone want to add the documentation for it. While this document isn't Ubuntu-specific, this is being purposefully done to allow it to apply to the vast majority of Podman users, regardless of what Linux distro they use.
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Podman > 4.9
|
||||
- Systemd > 250 (for quadlet support)
|
||||
- a restricted service account
|
||||
|
||||
|
||||
### Creating a Service Account
|
||||
|
||||
See [Creating a Restricted Service Account](INSTALL_MANUAL.md#creating-a-restricted-service-account) for instructions.
|
||||
|
||||
While you do not need to use the same account as the collector, this guide will assume you will be for all its examples.
|
||||
|
||||
In addition to those steps, you will need to create sub ids and enable lingering for the user:
|
||||
|
||||
```sh
|
||||
# add sub-uids and sub-gids, you may need to adjust numbers if you have other rootless quadlets running for other users already
|
||||
# it is not recommended to go below 100000
|
||||
# we choose to start at 500000 in the event you have some other podman accounts
|
||||
sudo usermod --add-subuids 500000-565535 scrutiny-svc
|
||||
sudo usermod --add-subgids 500000-565535 scrutiny-svc
|
||||
|
||||
# We want the quadlets to stay running even if the user isn't logged in
|
||||
sudo loginctl enable-linger scrutiny-svc
|
||||
```
|
||||
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Once the account is created, you will need to grab its id to create a few drectories for the data files and rootless quadlet files:
|
||||
|
||||
```sh
|
||||
# create folders for config and influxdb
|
||||
sudo mkdir -p /opt/scrutiny-svc/scrutiny/{config,influxdb}
|
||||
|
||||
# get the config file for scrutiny hub
|
||||
sudo wget -O /opt/scrutiny-svc/scrutiny/config/scrutiny.yaml https://raw.githubusercontent.com/AnalogJ/scrutiny/refs/heads/master/example.scrutiny.yaml
|
||||
|
||||
# set permissions on everything
|
||||
sudo chown -R scrutiny-svc:scrutiny-svc /opt/scrutiny-svc
|
||||
|
||||
# Get the ID of scrutiny-svc so you know it for your own record-keeping
|
||||
id -u scrutiny-svc
|
||||
|
||||
# create a directory
|
||||
sudo mkdir -p /etc/containers/systemd/users/$(id -u scrutiny-svc)
|
||||
|
||||
## go into the directory you just created for the rest of the guide
|
||||
cd /etc/containers/systemd/users/$(id -u scrutiny-svc)
|
||||
```
|
||||
|
||||
|
||||
### Quadlet Files
|
||||
|
||||
Now that everything is set up and configured for the account to run quadlets, we just need to create a few quadlet files.
|
||||
|
||||
All remaining system actions will take place in `/etc/containers/systemd/users/$(id -u scrutiny-svc)` which is why we had you cd into it.
|
||||
|
||||
|
||||
#### Networking
|
||||
|
||||
We need the hub and influxdb instances to be able to talk to each other, and in the case of Podman 4.9, they will run separately not sharing a localhost, and as such we need to configure a network for them to share. The file is pretty simple:
|
||||
|
||||
|
||||
##### scrutiny-net.network
|
||||
|
||||
```ini
|
||||
[Network]
|
||||
NetworkName=scrutiny-net
|
||||
```
|
||||
|
||||
|
||||
#### Containers
|
||||
|
||||
Now we're ready for creating the containers
|
||||
|
||||
|
||||
##### influxdb.container
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=influxdb
|
||||
|
||||
[Container]
|
||||
ContainerName=influxdb
|
||||
Image=docker.io/library/influxdb:2.8
|
||||
AutoUpdate=registry
|
||||
Timezone=local
|
||||
## not strictly necessary, but keeps file permission sane for influxdb
|
||||
PodmanArgs=--group-add keep-groups
|
||||
## versions of podman after 5.1 should do the below instead
|
||||
#GroupAdd=keep-groups
|
||||
Volume=/opt/scrutiny-svc/scrutiny/influxdb:/var/lib/influxdb2:Z
|
||||
Network=scrutiny-net
|
||||
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
# Start by default on boot
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
|
||||
##### scrutiny-web.container
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=scrutiny-web
|
||||
After=influxdb.service
|
||||
Requires=influxdb.service
|
||||
|
||||
[Container]
|
||||
ContainerName=scrutiny-web
|
||||
Image=ghcr.io/analogj/scrutiny:latest-web
|
||||
AutoUpdate=registry
|
||||
Timezone=local
|
||||
Volume=/opt/scrutiny-svc/scrutiny/config:/opt/scrutiny/config:Z
|
||||
Network=scrutiny-net
|
||||
PublishPort=8080:8080/tcp
|
||||
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
# Start by default on boot
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
#### Update scrutiny config
|
||||
|
||||
Since our containers are running separately, we need to update `/opt/scrutiny-svc/scrutiny/config/scrutiny.yaml` to the new influxdb host:
|
||||
|
||||
1. edit `/opt/scrutiny-svc/scrutiny/config/scrutiny.yaml`
|
||||
2. under `influxdb` section, change `host: 0.0.0.0` to `host: influxdb` -- remember that yaml is whitespace-sensitive! so be mindful of the indents
|
||||
|
||||
```yaml
|
||||
influxdb:
|
||||
# scheme: 'http'
|
||||
host: influxdb
|
||||
port: 8086
|
||||
```
|
||||
|
||||
# Running the hub and doing the
|
||||
|
||||
With that done, we're now ready to start up the services:
|
||||
|
||||
```sh
|
||||
# reload all the systemd user files for scrutiny-svc
|
||||
sudo systemctl --user -M scrutiny-svc@ daemon-reload
|
||||
|
||||
# start the scrutiny-net network:
|
||||
sudo systemctl --user -M scrutiny-svc@ start scrutiny-net-network.service
|
||||
|
||||
# start influxdb first and wait for it to come up
|
||||
sudo systemctl --user -M scrutiny-svc@ start influxdb.service
|
||||
|
||||
# check if it's fully up
|
||||
sudo systemctl --user -M scrutiny-svc@ status influxdb.service
|
||||
|
||||
# now start scrutiny
|
||||
sudo systemctl --user -M scrutiny-svc@ start scrutiny-web.service
|
||||
```
|
||||
|
||||
You are now ready to run the collector, if you would like to run that rootless as well, see the guide at [Schedule Collector with Systemd (rootless)](INSTALL_MANUAL.md#schedule-collector-with-systemd-rootless)
|
||||
@@ -41,14 +41,14 @@ The growth rate is pretty unintuitive -- see https://github.com/AnalogJ/scrutiny
|
||||
|
||||
InfluxDB is a required dependency for Scrutiny v0.4.0+.
|
||||
|
||||
https://docs.influxdata.com/influxdb/v2.2/install/
|
||||
https://docs.influxdata.com/influxdb/v2/install/
|
||||
|
||||
## Persistence
|
||||
|
||||
To ensure that all data is correctly stored, you must also persist the InfluxDB database directory
|
||||
|
||||
- If you're using the Official Scrutiny Omnibus image (`ghcr.io/analogj/scrutiny:master-omnibus`), the path is `/opt/scrutiny/influxdb`
|
||||
- If you're deploying in Hub/Spoke mode with the InfluxDB maintained image (`influxdb:2.2`), the path is `/var/lib/influxdb2`
|
||||
- If you're deploying in Hub/Spoke mode with the InfluxDB maintained image (`influxdb:2.8`), the path is `/var/lib/influxdb2`
|
||||
|
||||
If you attempt to restart Scrutiny but you forgot to persist the InfluxDB directory, you will get an error message like follows:
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
As documented in [example.scrutiny.yaml](https://github.com/AnalogJ/scrutiny/blob/master/example.scrutiny.yaml#L59-L75)
|
||||
there are multiple ways to configure notifications for Scrutiny.
|
||||
|
||||
Under the hood we use a library called [Shoutrrr](https://github.com/containrrr/shoutrrr) to send our notifications, and you should use their documentation if you run into
|
||||
any issues: https://containrrr.dev/shoutrrr/services/overview/
|
||||
Under the hood we use a library called [Shoutrrr](https://github.com/nicholas-fedor/shoutrrr) to send our notifications, and you should use their documentation if you run into
|
||||
any issues: https://shoutrrr.nickfedor.com/services/overview/
|
||||
|
||||
|
||||
# Script Notifications
|
||||
|
||||
+24
-39
@@ -59,7 +59,7 @@ log:
|
||||
|
||||
|
||||
# Notification "urls" look like the following. For more information about service specific configuration see
|
||||
# Shoutrrr's documentation: https://containrrr.dev/shoutrrr/services/overview/
|
||||
# Shoutrrr's documentation: https://shoutrrr.nickfedor.com/services/overview/
|
||||
#
|
||||
# note, usernames and passwords containing special characters will need to be urlencoded.
|
||||
# if your username is: "myname@example.com" and your password is "124@34$1"
|
||||
@@ -67,41 +67,26 @@ log:
|
||||
|
||||
#notify:
|
||||
# urls:
|
||||
# - "discord://token@webhookid"
|
||||
# - "telegram://token@telegram?channels=channel-1[,channel-2,...]"
|
||||
# - "pushover://shoutrrr:apiToken@userKey/?priority=1&devices=device1[,device2, ...]"
|
||||
# - "slack://[botname@]token-a/token-b/token-c"
|
||||
# - "smtp://username:password@host:port/?fromAddress=fromAddress&toAddresses=recipient1[,recipient2,...]"
|
||||
# - "teams://token-a/token-b/token-c"
|
||||
# - "gotify://gotify-host/token"
|
||||
# - "pushbullet://api-token[/device/#channel/email]"
|
||||
# - "ifttt://key/?events=event1[,event2,...]&value1=value1&value2=value2&value3=value3"
|
||||
# - "mattermost://[username@]mattermost-host/token[/channel]"
|
||||
# - "ntfy://username:password@host:port/topic"
|
||||
# - "hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz"
|
||||
# - "zulip://bot-mail:bot-key@zulip-domain/?stream=name-or-id&topic=name"
|
||||
# - "join://shoutrrr:api-key@join/?devices=device1[,device2, ...][&icon=icon][&title=title]"
|
||||
# - "script:///file/path/on/disk"
|
||||
# - "https://www.example.com/path"
|
||||
|
||||
########################################################################################################################
|
||||
# FEATURES COMING SOON
|
||||
#
|
||||
# The following commented out sections are a preview of additional configuration options that will be available soon.
|
||||
#
|
||||
########################################################################################################################
|
||||
|
||||
#limits:
|
||||
# ata:
|
||||
# critical:
|
||||
# error: 10
|
||||
# standard:
|
||||
# error: 20
|
||||
# warn: 10
|
||||
# scsi:
|
||||
# critical: true
|
||||
# standard: true
|
||||
# nvme:
|
||||
# critical: true
|
||||
# standard: true
|
||||
|
||||
# - discord://token@id[?thread_id=threadid]
|
||||
# - googlechat://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz
|
||||
# - hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz
|
||||
# - lark://host/token?secret=secret&title=title&link=url
|
||||
# - matrix://username:password@host:port/[?rooms=!roomID1[,roomAlias2]]
|
||||
# - mattermost://[username@]mattermost-host/token[/channel]
|
||||
# - rocketchat://[username@]rocketchat-host/token[/channel|@recipient]
|
||||
# - signal://[user[:password]@]host[:port]/source_phone/recipient1[,recipient2,...]
|
||||
# - slack://[botname@]token-a/token-b/token-c
|
||||
# - teams://group@tenant/altId/groupOwner?host=organization.webhook.office.com
|
||||
# - telegram://token@telegram?chats=@channel-1[,chat-id-1,chat-id-2:message-thread-id,...]
|
||||
# - wecom://key
|
||||
# - zulip://bot-mail:bot-key@zulip-domain/?stream=name-or-id&topic=name
|
||||
# - bark://devicekey@host
|
||||
# - gotify://gotify-host/token
|
||||
# - ifttt://key/?events=event1[,event2,...]&value1=value1&value2=value2&value3=value3
|
||||
# - join://shoutrrr:api-key@join/?devices=device1[,device2, ...][&icon=icon][&title=title]
|
||||
# - ntfy://username:password@ntfy.sh/topic
|
||||
# - pushbullet://api-token[/device/#channel/email]
|
||||
# - pushover://shoutrrr:apiToken@userKey/?devices=device1[,device2, ...]
|
||||
# - opsgenie://host/token?responders=responder1[,responder2]
|
||||
# - pagerduty://[host[:port]]/integration-key[?query-parameters]
|
||||
# - smtp://username:password@host:port/?fromaddress=fromAddress&toaddresses=recipient1[,recipient2,...][&additional_params]
|
||||
|
||||
@@ -4,7 +4,6 @@ go 1.25
|
||||
|
||||
require (
|
||||
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
@@ -12,6 +11,7 @@ require (
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0
|
||||
github.com/jaypipes/ghw v0.21.2
|
||||
github.com/nicholas-fedor/shoutrrr v0.13.2
|
||||
github.com/samber/lo v1.52.0
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/viper v1.21.0
|
||||
@@ -41,7 +41,6 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect
|
||||
github.com/jaypipes/pcidb v1.1.1 // indirect
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b h1:Y/+MfmdKPPpVY7C6ggt/FpltFSitlpUtyJEdcQyFXQg=
|
||||
github.com/analogj/go-util v0.0.0-20210417161720-39b497cca03b/go.mod h1:bRSzJXgXnT5+Ihah7RSC7Cvp16UmoLn3wq6ROciS1Ow=
|
||||
@@ -12,8 +14,6 @@ github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiD
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
|
||||
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -40,8 +40,8 @@ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GM
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5 h1:1OyorA5LtdQw12cyJDEHuTrEV3GiXiIhS4/QTTa/SM8=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v7fN3D9d3eEOAXRhi/+M=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
@@ -53,21 +53,19 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
|
||||
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@@ -76,8 +74,8 @@ github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjw
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
|
||||
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
||||
github.com/jaypipes/ghw v0.21.2 h1:woW0lqNMPbYk59sur6thOVM8YFP9Hxxr8PM+JtpUrNU=
|
||||
github.com/jaypipes/ghw v0.21.2/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE=
|
||||
github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro=
|
||||
@@ -116,12 +114,14 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nicholas-fedor/shoutrrr v0.13.2 h1:hfsYBIqSFYGg92pZP5CXk/g7/OJIkLYmiUnRl+AD1IA=
|
||||
github.com/nicholas-fedor/shoutrrr v0.13.2/go.mod h1:ZqzV3gY/Wj6AvWs1etlO7+yKbh4iptSbeL8avBpMQbA=
|
||||
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
||||
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
||||
@@ -30,6 +30,7 @@ const (
|
||||
// 60seconds * 60minutes * 24hours * 7 days * (52 + 52 + 4)weeks
|
||||
RETENTION_PERIOD_25_MONTHS_IN_SECONDS = 65_318_400
|
||||
|
||||
DURATION_KEY_DAY = "day"
|
||||
DURATION_KEY_WEEK = "week"
|
||||
DURATION_KEY_MONTH = "month"
|
||||
DURATION_KEY_YEAR = "year"
|
||||
@@ -446,6 +447,7 @@ func (sr *scrutinyRepository) GetSummary(ctx context.Context) (map[string]*model
|
||||
|
||||
func (sr *scrutinyRepository) lookupBucketName(durationKey string) string {
|
||||
switch durationKey {
|
||||
case DURATION_KEY_DAY:
|
||||
case DURATION_KEY_WEEK:
|
||||
//data stored in the last week
|
||||
return sr.appConfig.GetString("web.influxdb.bucket")
|
||||
@@ -463,8 +465,10 @@ func (sr *scrutinyRepository) lookupBucketName(durationKey string) string {
|
||||
}
|
||||
|
||||
func (sr *scrutinyRepository) lookupDuration(durationKey string) []string {
|
||||
|
||||
switch durationKey {
|
||||
case DURATION_KEY_DAY:
|
||||
//data stored in the last day
|
||||
return []string{"-1d", "now()"}
|
||||
case DURATION_KEY_WEEK:
|
||||
//data stored in the last week
|
||||
return []string{"-1w", "now()"}
|
||||
@@ -481,8 +485,22 @@ func (sr *scrutinyRepository) lookupDuration(durationKey string) []string {
|
||||
return []string{"-1w", "now()"}
|
||||
}
|
||||
|
||||
func (sr *scrutinyRepository) lookupResolution(durationKey string) string {
|
||||
switch durationKey {
|
||||
case DURATION_KEY_DAY:
|
||||
// Return data with higher resolution for daily summaries
|
||||
return "10m"
|
||||
default:
|
||||
// Return data with 1h resolution for other summaries
|
||||
return "1h"
|
||||
}
|
||||
}
|
||||
|
||||
func (sr *scrutinyRepository) lookupNestedDurationKeys(durationKey string) []string {
|
||||
switch durationKey {
|
||||
case DURATION_KEY_DAY:
|
||||
//all data is stored in a single bucket, but we want a finer resolution
|
||||
return []string{DURATION_KEY_DAY}
|
||||
case DURATION_KEY_WEEK:
|
||||
//all data is stored in a single bucket
|
||||
return []string{DURATION_KEY_WEEK}
|
||||
|
||||
@@ -177,7 +177,7 @@ func (sr *scrutinyRepository) aggregateSmartAttributesQuery(wwn string, duration
|
||||
`|> sort(columns: ["_time"], desc: true)`,
|
||||
}...)
|
||||
if selectEntries > 0 {
|
||||
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> tail(n: %d, offset: %d)`, selectEntries, selectEntriesOffset))
|
||||
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> limit(n: %d, offset: %d)`, selectEntries, selectEntriesOffset))
|
||||
}
|
||||
partialQueryStr = append(partialQueryStr, `|> yield(name: "last")`)
|
||||
|
||||
@@ -196,9 +196,11 @@ func (sr *scrutinyRepository) generateSmartAttributesSubquery(wwn string, durati
|
||||
}
|
||||
|
||||
partialQueryStr = append(partialQueryStr, `|> aggregateWindow(every: 1d, fn: last, createEmpty: false)`)
|
||||
|
||||
|
||||
// ensure we are selecting the latest entries when paging
|
||||
partialQueryStr = append(partialQueryStr, `|> sort(columns: ["_time"], desc: true)`)
|
||||
if selectEntries > 0 {
|
||||
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> tail(n: %d, offset: %d)`, selectEntries, selectEntriesOffset))
|
||||
partialQueryStr = append(partialQueryStr, fmt.Sprintf(`|> limit(n: %d, offset: %d)`, selectEntries, selectEntriesOffset))
|
||||
}
|
||||
partialQueryStr = append(partialQueryStr, "|> schema.fieldsAsCols()")
|
||||
|
||||
|
||||
@@ -140,13 +140,14 @@ func (sr *scrutinyRepository) aggregateTempQuery(durationKey string) string {
|
||||
for _, nestedDurationKey := range nestedDurationKeys {
|
||||
bucketName := sr.lookupBucketName(nestedDurationKey)
|
||||
durationRange := sr.lookupDuration(nestedDurationKey)
|
||||
durationResolution := sr.lookupResolution(nestedDurationKey)
|
||||
|
||||
subQueryNames = append(subQueryNames, fmt.Sprintf(`%sData`, nestedDurationKey))
|
||||
partialQueryStr = append(partialQueryStr, []string{
|
||||
fmt.Sprintf(`%sData = from(bucket: "%s")`, nestedDurationKey, bucketName),
|
||||
fmt.Sprintf(`|> range(start: %s, stop: %s)`, durationRange[0], durationRange[1]),
|
||||
`|> filter(fn: (r) => r["_measurement"] == "temp" )`,
|
||||
`|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)`,
|
||||
fmt.Sprintf(`|> aggregateWindow(every: %s, fn: mean, createEmpty: false)`, durationResolution),
|
||||
`|> group(columns: ["device_wwn"])`,
|
||||
`|> toInt()`,
|
||||
"",
|
||||
|
||||
@@ -143,21 +143,21 @@ type SmartInfo struct {
|
||||
ErrorNumber int `json:"error_number"`
|
||||
LifetimeHours int `json:"lifetime_hours"`
|
||||
CompletionRegisters struct {
|
||||
Error int `json:"error"`
|
||||
Status int `json:"status"`
|
||||
Count int `json:"count"`
|
||||
Lba int `json:"lba"`
|
||||
Device int `json:"device"`
|
||||
Error int `json:"error"`
|
||||
Status int `json:"status"`
|
||||
Count int `json:"count"`
|
||||
Lba uint64 `json:"lba"`
|
||||
Device int `json:"device"`
|
||||
} `json:"completion_registers"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
PreviousCommands []struct {
|
||||
Registers struct {
|
||||
Command int `json:"command"`
|
||||
Features int `json:"features"`
|
||||
Count int `json:"count"`
|
||||
Lba int `json:"lba"`
|
||||
Device int `json:"device"`
|
||||
DeviceControl int `json:"device_control"`
|
||||
Command int `json:"command"`
|
||||
Features int `json:"features"`
|
||||
Count int `json:"count"`
|
||||
Lba uint64 `json:"lba"`
|
||||
Device int `json:"device"`
|
||||
DeviceControl int `json:"device_control"`
|
||||
} `json:"registers"`
|
||||
PowerupMilliseconds int `json:"powerup_milliseconds"`
|
||||
CommandName string `json:"command_name"`
|
||||
@@ -188,8 +188,8 @@ type SmartInfo struct {
|
||||
AtaSmartSelectiveSelfTestLog struct {
|
||||
Revision int `json:"revision"`
|
||||
Table []struct {
|
||||
LbaMin int `json:"lba_min"`
|
||||
LbaMax int `json:"lba_max"`
|
||||
LbaMin uint64 `json:"lba_min"`
|
||||
LbaMax uint64 `json:"lba_max"`
|
||||
Status struct {
|
||||
Value int `json:"value"`
|
||||
String string `json:"string"`
|
||||
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
|
||||
"github.com/analogj/scrutiny/webapp/backend/pkg/thresholds"
|
||||
"github.com/containrrr/shoutrrr"
|
||||
shoutrrrTypes "github.com/containrrr/shoutrrr/pkg/types"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicholas-fedor/shoutrrr"
|
||||
shoutrrrTypes "github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -424,6 +424,17 @@ func (n *Notify) GenShoutrrrNotificationParams(shoutrrrUrl string) (string, *sho
|
||||
case "telegram":
|
||||
(*params)["title"] = subject
|
||||
case "zulip":
|
||||
query := serviceURL.Query()
|
||||
urlTopic := query["topic"]
|
||||
delete(query, "topic")
|
||||
if len(urlTopic) > 0 && urlTopic[len(urlTopic)-1] != "" {
|
||||
subject = urlTopic[len(urlTopic)-1]
|
||||
}
|
||||
subjectRunes := []rune(subject)
|
||||
if len(subjectRunes) > 60 {
|
||||
n.Logger.Warningf("Zulip notification subject too long (%d characters), truncating to 60 characters", len(subjectRunes))
|
||||
subject = string(subjectRunes[:60])
|
||||
}
|
||||
(*params)["topic"] = subject
|
||||
}
|
||||
|
||||
|
||||
Generated
+5
-4
@@ -7484,10 +7484,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
|
||||
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==",
|
||||
"dev": true
|
||||
"version": "4.3.8",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz",
|
||||
"integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
<button (click)="changeSummaryTempDuration('year')" mat-menu-item>year</button>
|
||||
<button (click)="changeSummaryTempDuration('month')" mat-menu-item>month</button>
|
||||
<button (click)="changeSummaryTempDuration('week')" mat-menu-item>week</button>
|
||||
<button (click)="changeSummaryTempDuration('day')" mat-menu-item>day</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
summaryData: { [key: string]: DeviceSummaryModel };
|
||||
hostGroups: { [hostId: string]: string[] } = {}
|
||||
temperatureOptions: ApexOptions;
|
||||
tempDurationKey = 'forever'
|
||||
tempDurationKey = 'week'
|
||||
config: AppConfig;
|
||||
showArchived: boolean;
|
||||
|
||||
@@ -272,11 +272,11 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
DURATION_KEY_DAY = "day"
|
||||
DURATION_KEY_WEEK = "week"
|
||||
DURATION_KEY_MONTH = "month"
|
||||
DURATION_KEY_YEAR = "year"
|
||||
DURATION_KEY_FOREVER = "forever"
|
||||
DURATION_KEY_MONTH = "month"
|
||||
DURATION_KEY_YEAR = "year"
|
||||
DURATION_KEY_FOREVER = "forever"
|
||||
*/
|
||||
|
||||
changeSummaryTempDuration(durationKey: string): void {
|
||||
|
||||
Reference in New Issue
Block a user