First release: restic-exporter 1.0.0
This commit is contained in:
parent
5f23dcb5f1
commit
69c7485071
13 changed files with 1581 additions and 2 deletions
20
.github/workflows/autotag.yml
vendored
Normal file
20
.github/workflows/autotag.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
name: autotag
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Auto Tag
|
||||
uses: Klemensas/action-autotag@stable
|
||||
with:
|
||||
GITHUB_TOKEN: "${{ secrets.GH_PAT }}"
|
||||
tag_prefix: ""
|
52
.github/workflows/release-docker.yml
vendored
Normal file
52
.github/workflows/release-docker.yml
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
name: release-docker
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*.*.*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Downcase repo
|
||||
run: echo REPOSITORY=ngosang/restic-exporter >> $GITHUB_ENV
|
||||
-
|
||||
name: Docker metadata
|
||||
id: docker_metadata
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ env.REPOSITORY }},ghcr.io/${{ env.REPOSITORY }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1.0.1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_PAT }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/386, linux/amd64, linux/arm/v6, linux/arm/v7, linux/arm64/v8, linux/ppc64le, linux/s390x
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_metadata.outputs.tags }}
|
||||
labels: ${{ steps.docker_metadata.outputs.labels }}
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
*.pyc
|
||||
.settings
|
||||
.directory
|
||||
.idea/
|
||||
_trial_temp/
|
||||
.vscode
|
||||
.run/
|
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
## 1.0.0 (2022/12/06)
|
||||
|
||||
* First release
|
||||
* Restic 0.14.0
|
41
Dockerfile
Normal file
41
Dockerfile
Normal file
|
@ -0,0 +1,41 @@
|
|||
FROM golang:alpine3.17 AS builder
|
||||
|
||||
ENV RESTIC_VERSION 0.14.0
|
||||
ENV CGO_ENABLED 0
|
||||
|
||||
RUN cd /tmp \
|
||||
# download restic source code
|
||||
&& wget https://github.com/restic/restic/archive/refs/tags/v${RESTIC_VERSION}.tar.gz -O restic.tar.gz \
|
||||
&& tar xvf restic.tar.gz \
|
||||
&& cd restic-* \
|
||||
# build the executable
|
||||
# flag -ldflags "-s -w" produces a smaller executable
|
||||
&& go build -ldflags "-s -w" -v -o /tmp/restic ./cmd/restic
|
||||
|
||||
FROM python:3.11-alpine3.17
|
||||
|
||||
RUN apk add --no-cache --update tzdata
|
||||
|
||||
COPY --from=builder /tmp/restic /usr/bin
|
||||
COPY entrypoint.sh requirements.txt /
|
||||
|
||||
RUN pip install -r /requirements.txt \
|
||||
# remove temporary files
|
||||
&& rm -rf /root/.cache
|
||||
|
||||
COPY ./restic-exporter.py /restic-exporter.py
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
CMD [ "/entrypoint.sh" ]
|
||||
|
||||
# Help
|
||||
#
|
||||
# Local build
|
||||
# docker build -t restic-exporter:custom .
|
||||
#
|
||||
# Multi-arch build
|
||||
# docker buildx create --use
|
||||
# docker buildx build -t restic-exporter:custom --platform linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x .
|
||||
#
|
||||
# add --push to publish in DockerHub
|
164
README.md
164
README.md
|
@ -1,2 +1,162 @@
|
|||
# restic-exporter
|
||||
Prometheus exporter for the Restic backup system
|
||||
# ngosang/restic-exporter
|
||||
|
||||
[![Latest release](https://img.shields.io/github/v/release/ngosang/restic-exporter)](https://github.com/ngosang/restic-exporter/releases)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/ngosang/restic-exporter)](https://hub.docker.com/r/ngosang/restic-exporter/)
|
||||
[![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-yellow.svg)](https://www.paypal.com/paypalme/diegoheras0xff)
|
||||
[![Donate Bitcoin](https://img.shields.io/badge/Donate-Bitcoin-f7931a.svg)](https://www.blockchain.com/btc/address/14EcPN47rWXkmFvjfohJx2rQxxoeBRJhej)
|
||||
[![Donate Ethereum](https://img.shields.io/badge/Donate-Ethereum-8c8c8c.svg)](https://www.blockchain.com/eth/address/0x0D1549BbB00926BF3D92c1A8A58695e982f1BE2E)
|
||||
|
||||
Prometheus exporter for the [Restic](https://github.com/restic/restic) backup system.
|
||||
|
||||
## Install
|
||||
|
||||
### Form source code
|
||||
|
||||
Requirements:
|
||||
* Python 3
|
||||
* [prometheus-client](https://github.com/prometheus/client_python)
|
||||
|
||||
```bash
|
||||
pip install -r /requirements.txt
|
||||
|
||||
export RESTIC_REPO_URL=/data
|
||||
export PASSWORD_FILE=/restic_password_file
|
||||
python restic-exporter.py
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Docker images are available in [GHCR](https://github.com/ngosang/restic-exporter/pkgs/container/restic-exporter) and [DockerHub](https://hub.docker.com/r/ngosang/restic-exporter).
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/ngosang/restic-exporter
|
||||
or
|
||||
docker pull ngosang/restic-exporter
|
||||
```
|
||||
|
||||
#### Supported Architectures
|
||||
|
||||
The architectures supported by this image are:
|
||||
|
||||
* linux/386
|
||||
* linux/amd64
|
||||
* linux/arm/v6
|
||||
* linux/arm/v7
|
||||
* linux/arm64/v8
|
||||
* linux/ppc64le
|
||||
* linux/s390x
|
||||
|
||||
#### docker-compose
|
||||
|
||||
Compatible with docker-compose v2 schemas:
|
||||
|
||||
```yaml
|
||||
---
|
||||
version: '2.1'
|
||||
services:
|
||||
restic-exporter:
|
||||
image: ngosang/restic-exporter
|
||||
container_name: restic-exporter
|
||||
environment:
|
||||
- TZ=Europe/Madrid
|
||||
- RESTIC_REPO_URL=/data
|
||||
- RESTIC_REPO_PASSWORD=<password_here>
|
||||
# - RESTIC_REPO_PASSWORD_FILE=</file_with_password_here>
|
||||
- REFRESH_INTERVAL=1800 # 30 min
|
||||
volumes:
|
||||
- /host_path/restic/data:/data
|
||||
ports:
|
||||
- "8001:8001"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
#### docker cli
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name=restic-exporter \
|
||||
-e TZ=Europe/Madrid \
|
||||
-e RESTIC_REPO_URL=/data \
|
||||
-e RESTIC_REPO_PASSWORD=<password_here> \
|
||||
-e REFRESH_INTERVAL=1800 \
|
||||
-p 8001:8001 \
|
||||
--restart unless-stopped \
|
||||
ngosang/restic-exporter
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration is done with environment variables.
|
||||
|
||||
- `RESTIC_REPO_URL`: Restic repository URL. It could be a local repository (eg: `/data`) or a remote repository (eg: `rest:http://user:password@127.0.0.1:8000/`).
|
||||
- `RESTIC_REPO_PASSWORD`: Restic repository password in plain text. This is only required if `RESTIC_REPO_PASSWORD_FILE` is not defined.
|
||||
- `RESTIC_REPO_PASSWORD_FILE`: File with the Restic repository password in plain text. This is only required if `RESTIC_REPO_PASSWORD` is not defined. Remember to mount the Docker volume with the file.
|
||||
- `REFRESH_INTERVAL`: (Optional) Refresh interval for the metrics in seconds. Computing the metrics is a expensive task, keep this value as high as possible. Default 60
|
||||
- `LISTEN_PORT`: (Optional) The address the exporter should listen on. The default is `8001`.
|
||||
- `LISTEN_ADDRESS`: (Optional) The address the exporter should listen on. The default is to listen on all addresses.
|
||||
- `LOG_LEVEL`: (Optional) Log level of the traces. The default is `INFO`.
|
||||
|
||||
## Exported metrics
|
||||
|
||||
```shell
|
||||
# HELP restic_check_success Result of restic check operation in the repository
|
||||
# TYPE restic_check_success gauge
|
||||
restic_check_success 1.0
|
||||
# HELP restic_snapshots_total Total number of snapshots in the repository
|
||||
# TYPE restic_snapshots_total counter
|
||||
restic_snapshots_total 1777.0
|
||||
# HELP restic_backup_timestamp Timestamp of the last backup
|
||||
# TYPE restic_backup_timestamp gauge
|
||||
restic_backup_timestamp{client_hostname="PC-HOME-1",client_username="PC-HOME-1\\User-1",snapshot_hash="1911eb846f1642c327936915f1fad4e16190d0ab6b68e045294f5f0280a00ebe"} 1.669754009e+09
|
||||
# HELP restic_backup_files_total Number of files in the backup
|
||||
# TYPE restic_backup_files_total counter
|
||||
restic_backup_files_total{client_hostname="PC-HOME-1",client_username="PC-HOME-1\\User-1",snapshot_hash="1911eb846f1642c327936915f1fad4e16190d0ab6b68e045294f5f0280a00ebe"} 19051.0
|
||||
# HELP restic_backup_size_total Total size of backup in bytes
|
||||
# TYPE restic_backup_size_total counter
|
||||
restic_backup_size_total{client_hostname="PC-HOME-1",client_username="PC-HOME-1\\User-1",snapshot_hash="1911eb846f1642c327936915f1fad4e16190d0ab6b68e045294f5f0280a00ebe"} 4.1174838248e+010
|
||||
# HELP restic_backup_snapshots_total Total number of snapshots
|
||||
# TYPE restic_backup_snapshots_total counter
|
||||
restic_backup_snapshots_total{client_hostname="PC-HOME-1",client_username="PC-HOME-1\\User-1",snapshot_hash="1911eb846f1642c327936915f1fad4e16190d0ab6b68e045294f5f0280a00ebe"} 106.0
|
||||
```
|
||||
|
||||
## Prometheus config
|
||||
|
||||
Example Prometheus configuration:
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'restic-exporter'
|
||||
static_configs:
|
||||
- targets: ['192.168.1.100:8001']
|
||||
```
|
||||
|
||||
## Prometheus / Alertmanager rules
|
||||
|
||||
Example Prometheus rules for alerting:
|
||||
|
||||
```yaml
|
||||
- alert: ResticCheckFailed
|
||||
expr: restic_check_success == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: Restic check failed (instance {{ $labels.instance }})
|
||||
description: Restic check failed\n VALUE = {{ $value }}\n LABELS = {{ $labels }}
|
||||
|
||||
- alert: ResticOutdatedBackup
|
||||
# 1209600 = 15 days
|
||||
expr: time() - restic_backup_timestamp > 1209600
|
||||
for: 0m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: Restic {{ $labels.client_hostname }} / {{ $labels.client_username }} backup is outdated
|
||||
description: Restic backup is outdated\n VALUE = {{ $value }}\n LABELS = {{ $labels }}
|
||||
```
|
||||
|
||||
## Grafana dashboard
|
||||
|
||||
There is a reference Grafana dashboard in [grafana/grafana_dashboard.json](./grafana/grafana_dashboard.json).
|
||||
|
||||
![](./grafana/grafana_dashboard.png)
|
||||
|
|
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
version: "2.1"
|
||||
|
||||
services:
|
||||
restic-exporter:
|
||||
image: ngosang/restic-exporter
|
||||
container_name: restic-exporter
|
||||
environment:
|
||||
- TZ=Europe/Madrid
|
||||
- RESTIC_REPO_URL=/data
|
||||
- RESTIC_REPO_PASSWORD=password_here
|
||||
# - RESTIC_REPO_PASSWORD_FILE=/file_with_password_here
|
||||
- REFRESH_INTERVAL=1800 # 30 min
|
||||
volumes:
|
||||
- /host_path/restic/data:/data
|
||||
ports:
|
||||
- "8001:8001"
|
||||
restart: unless-stopped
|
18
entrypoint.sh
Executable file
18
entrypoint.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Exit on error. For debug use set -x
|
||||
set -e
|
||||
|
||||
export PASSWORD_FILE="/tmp/restic_passwd"
|
||||
|
||||
if [ -z "${RESTIC_REPO_PASSWORD}" ]; then
|
||||
if [ -z "${RESTIC_REPO_PASSWORD_FILE}" ]; then
|
||||
echo "You have to define one of these environment variables: RESTIC_REPO_PASSWORD or RESTIC_REPO_PASSWORD_FILE"
|
||||
else
|
||||
cp "${RESTIC_REPO_PASSWORD_FILE}" "${PASSWORD_FILE}"
|
||||
fi
|
||||
else
|
||||
echo "${RESTIC_REPO_PASSWORD}" > "${PASSWORD_FILE}"
|
||||
fi
|
||||
|
||||
/usr/local/bin/python -u /restic-exporter.py
|
1019
grafana/grafana_dashboard.json
Normal file
1019
grafana/grafana_dashboard.json
Normal file
File diff suppressed because it is too large
Load diff
BIN
grafana/grafana_dashboard.png
Normal file
BIN
grafana/grafana_dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
5
package.json
Normal file
5
package.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "ngosang-restic-exporter",
|
||||
"version": "1.0.0",
|
||||
"author": "ngosang@hotmail.es"
|
||||
}
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
@ -0,0 +1 @@
|
|||
prometheus-client==0.15.0
|
233
restic-exporter.py
Normal file
233
restic-exporter.py
Normal file
|
@ -0,0 +1,233 @@
|
|||
#!/usr/bin/env python3
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import prometheus_client
|
||||
import prometheus_client.core
|
||||
|
||||
|
||||
class ResticCollector(object):
|
||||
def __init__(self, repository, password_file_):
|
||||
self.repository = repository
|
||||
self.password_file = password_file_
|
||||
# todo: the stats cache increases over time -> remove old ids
|
||||
# todo: cold start -> the stats cache could be saved in a persistent volume
|
||||
# todo: cold start -> the restic cache (/root/.cache/restic) could be saved in a persistent volume
|
||||
self.stats_cache = {}
|
||||
self.metrics = {}
|
||||
self.refresh()
|
||||
|
||||
def collect(self):
|
||||
logging.debug("Incoming request")
|
||||
|
||||
common_label_names = [
|
||||
"client_hostname",
|
||||
"client_username",
|
||||
"snapshot_hash"
|
||||
]
|
||||
|
||||
check_success = prometheus_client.core.GaugeMetricFamily(
|
||||
"restic_check_success",
|
||||
"Result of restic check operation in the repository",
|
||||
labels=[])
|
||||
|
||||
snapshots_total = prometheus_client.core.CounterMetricFamily(
|
||||
"restic_snapshots_total",
|
||||
"Total number of snapshots in the repository",
|
||||
labels=[])
|
||||
|
||||
backup_timestamp = prometheus_client.core.GaugeMetricFamily(
|
||||
"restic_backup_timestamp",
|
||||
"Timestamp of the last backup",
|
||||
labels=common_label_names)
|
||||
|
||||
backup_files_total = prometheus_client.core.CounterMetricFamily(
|
||||
"restic_backup_files_total",
|
||||
"Number of files in the backup",
|
||||
labels=common_label_names)
|
||||
|
||||
backup_size_total = prometheus_client.core.CounterMetricFamily(
|
||||
"restic_backup_size_total",
|
||||
"Total size of backup in bytes",
|
||||
labels=common_label_names)
|
||||
|
||||
backup_snapshots_total = prometheus_client.core.CounterMetricFamily(
|
||||
"restic_backup_snapshots_total",
|
||||
"Total number of snapshots",
|
||||
labels=common_label_names)
|
||||
|
||||
check_success.add_metric([], self.metrics["check_success"])
|
||||
snapshots_total.add_metric([], self.metrics["snapshots_total"])
|
||||
|
||||
for client in self.metrics['clients']:
|
||||
common_label_values = [
|
||||
client["hostname"],
|
||||
client["username"],
|
||||
client["snapshot_hash"]
|
||||
]
|
||||
backup_timestamp.add_metric(common_label_values, client["timestamp"])
|
||||
backup_files_total.add_metric(common_label_values, client["files_total"])
|
||||
backup_size_total.add_metric(common_label_values, client["size_total"])
|
||||
backup_snapshots_total.add_metric(common_label_values, client["snapshots_total"])
|
||||
|
||||
yield check_success
|
||||
yield snapshots_total
|
||||
yield backup_timestamp
|
||||
yield backup_files_total
|
||||
yield backup_size_total
|
||||
yield backup_snapshots_total
|
||||
|
||||
def refresh(self):
|
||||
try:
|
||||
self.metrics = self.get_metrics()
|
||||
except Exception as e:
|
||||
logging.error("Unable to collect metrics from Restic. Error: %s", str(e))
|
||||
|
||||
def get_metrics(self):
|
||||
all_snapshots = self.get_snapshots()
|
||||
latest_snapshots = self.get_snapshots(True)
|
||||
clients = []
|
||||
for snap in latest_snapshots:
|
||||
stats = self.get_stats(snap['id'])
|
||||
|
||||
time_parsed = re.sub(r'\.[^+-]+', '', snap['time'])
|
||||
timestamp = time.mktime(datetime.datetime.strptime(time_parsed, "%Y-%m-%dT%H:%M:%S%z").timetuple())
|
||||
|
||||
snapshots_total = 0
|
||||
for snap2 in all_snapshots:
|
||||
if snap2['hash'] == snap['hash']:
|
||||
snapshots_total += 1
|
||||
|
||||
clients.append({
|
||||
'snapshot_hash': snap['hash'],
|
||||
'hostname': snap['hostname'],
|
||||
'username': snap['username'],
|
||||
'timestamp': timestamp,
|
||||
'size_total': stats['total_size'],
|
||||
'files_total': stats['total_file_count'],
|
||||
'snapshots_total': snapshots_total
|
||||
})
|
||||
# todo: fix the commented code when the bug is fixed in restic
|
||||
# https://github.com/restic/restic/issues/2126
|
||||
# stats = self.get_stats()
|
||||
check_success = self.get_check()
|
||||
metrics = {
|
||||
'check_success': check_success,
|
||||
'clients': clients,
|
||||
# 'size_total': stats['total_size'],
|
||||
# 'files_total': stats['total_file_count'],
|
||||
'snapshots_total': len(all_snapshots)
|
||||
}
|
||||
return metrics
|
||||
|
||||
def get_snapshots(self, only_latest=False):
|
||||
cmd = [
|
||||
'restic',
|
||||
'-r', self.repository,
|
||||
'-p', self.password_file,
|
||||
'--no-lock',
|
||||
'snapshots', '--json'
|
||||
]
|
||||
if only_latest:
|
||||
cmd.extend(['--latest', '1'])
|
||||
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Error executing restic snapshot command. Exit code: " + str(result.returncode))
|
||||
snapshots = json.loads(result.stdout.decode('utf-8'))
|
||||
for snap in snapshots:
|
||||
snap['hash'] = self.calc_snapshot_hash(snap)
|
||||
return snapshots
|
||||
|
||||
def get_stats(self, snapshot_id=None):
|
||||
# This command is expensive in CPU/Memory (1-5 seconds),
|
||||
# and much more when snapshot_id=None (3 minutes) -> we avoid this call for now
|
||||
# https://github.com/restic/restic/issues/2126
|
||||
if snapshot_id is not None and snapshot_id in self.stats_cache:
|
||||
return self.stats_cache[snapshot_id]
|
||||
|
||||
cmd = [
|
||||
'restic',
|
||||
'-r', self.repository,
|
||||
'-p', self.password_file,
|
||||
'--no-lock',
|
||||
'stats', '--json'
|
||||
]
|
||||
if snapshot_id is not None:
|
||||
cmd.extend([snapshot_id])
|
||||
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
if result.returncode != 0:
|
||||
raise Exception("Error executing restic stats command. Exit code: " + str(result.returncode))
|
||||
stats = json.loads(result.stdout.decode('utf-8'))
|
||||
|
||||
if snapshot_id is not None:
|
||||
self.stats_cache[snapshot_id] = stats
|
||||
|
||||
return stats
|
||||
|
||||
def get_check(self):
|
||||
# This command takes 20 seconds or more, but it's required
|
||||
cmd = [
|
||||
'restic',
|
||||
'-r', self.repository,
|
||||
'-p', self.password_file,
|
||||
'--no-lock',
|
||||
'check'
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||||
if result.returncode == 0:
|
||||
return 1 # ok
|
||||
return 0 # error
|
||||
|
||||
def calc_snapshot_hash(self, snapshot: dict) -> str:
|
||||
text = snapshot['hostname'] + ",".join(snapshot['paths'])
|
||||
return hashlib.sha256(text.encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s %(levelname)-8s %(message)s',
|
||||
level=logging.getLevelName(os.environ.get("LOG_LEVEL", "INFO")),
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
logging.info("Starting Restic Prometheus Exporter ...")
|
||||
logging.info("It could take a while if the repository is remote.")
|
||||
|
||||
try:
|
||||
restic_repo_url = os.environ["RESTIC_REPO_URL"]
|
||||
except Exception:
|
||||
logging.error("Configuration error. The environment variable RESTIC_REPO_URL is mandatory")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
password_file = os.environ["PASSWORD_FILE"]
|
||||
except Exception:
|
||||
logging.error("Configuration error. The environment variable PASSWORD_FILE is mandatory")
|
||||
sys.exit(1)
|
||||
|
||||
exporter_address = os.environ.get("LISTEN_ADDRESS", "0.0.0.0")
|
||||
exporter_port = int(os.environ.get("LISTEN_PORT", 8001))
|
||||
exporter_refresh_interval = int(os.environ.get("REFRESH_INTERVAL", 60))
|
||||
|
||||
collector = ResticCollector(restic_repo_url, password_file)
|
||||
|
||||
prometheus_client.core.REGISTRY.register(collector)
|
||||
prometheus_client.start_http_server(exporter_port, exporter_address)
|
||||
|
||||
logging.info("Server listening in http://%s:%d/metrics", exporter_address, exporter_port)
|
||||
while True:
|
||||
logging.info("Refreshing stats every %d seconds", exporter_refresh_interval)
|
||||
time.sleep(exporter_refresh_interval)
|
||||
collector.refresh()
|
Loading…
Reference in a new issue