feat: up version

Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>
This commit is contained in:
martabal 2023-06-21 23:29:41 +02:00
parent cf29943ebe
commit 8a7ebfefa1
No known key found for this signature in database
GPG key ID: C00196E3148A52BD
20 changed files with 336 additions and 322 deletions

View file

@ -2,3 +2,6 @@ img/
grafana/
.github/
Dockerfile
README.md
.env*
.gitignore

View file

@ -1,3 +1,2 @@
IMMICH_USERNAME=
IMMICH_PASSWORD=
IMMICH_API_KEY=
IMMICH_BASE_URL=

17
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: Build
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
name: Run build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build
run: go build -o ./immich.out ./src

34
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: CodeQL
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: CodeQL Analysis
uses: github/codeql-action/analyze@v2

View file

@ -8,9 +8,7 @@ on:
type: string
jobs:
build_push_monolith:
build_docker_release:
runs-on: ubuntu-latest
steps:
- name: Checkout
@ -19,14 +17,12 @@ jobs:
ref: 'main'
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
@ -34,14 +30,21 @@ jobs:
username: martabal
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich
uses: docker/build-push-action@v3.2.0
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: ./
file: ./Dockerfile
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: |
martabal/immich-exporter:${{ inputs.tags }}
martabal/immich-exporter:latest
ghcr.io/${{ github.repository_owner }}/immich-exporter:${{ inputs.tags }}
ghcr.io/${{ github.repository_owner }}/immich-exporter:latest

17
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: Test
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
test:
name: Run tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run formatter
run: test -z $(gofmt -l ./src)

View file

@ -1,16 +1,17 @@
FROM golang:1.19-alpine3.17 AS builder
FROM golang:1.20-alpine3.18 AS builder
WORKDIR /app
COPY . .
RUN go get -d -v ./src/ && \
go build -o /go/bin/immich-exporter ./src
RUN go build -o /go/bin/immich-exporter ./src
FROM alpine:3.17
FROM alpine:3.18
COPY --from=builder /go/bin/immich-exporter /go/bin/immich-exporter
COPY package.json /go/bin/
WORKDIR /go/bin
CMD ["/go/bin/immich-exporter"]

View file

@ -1,6 +1,8 @@
# immich-exporter
[![Publish Release](https://github.com/martabal/immich-exporter/actions/workflows/push_docker.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/push_docker.yml)
[![Publish Release](https://github.com/martabal/immich-exporter/actions/workflows/docker.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/docker.yml)
[![Build](https://github.com/martabal/immich-exporter/actions/workflows/build.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/build.yml)
[![Test](https://github.com/martabal/immich-exporter/actions/workflows/test.yml/badge.svg)](https://github.com/martabal/immich-exporter/actions/workflows/test.yml)
<p align="center">
<img src="img/immich.png" width=100> &nbsp; <img src="img/prometheus.png" width=100><img src="img/golang.png" width=100>
@ -11,13 +13,14 @@ This app is made to be integrated with the [immich-grafana-dashboard](https://gi
## Run it
Create an API key in your Immich settings and set `IMMICH_API_KEY` to is value.
### Docker-cli ([click here for more info](https://docs.docker.com/engine/reference/commandline/cli/))
```sh
docker run --name=immich-exporter \
-e IMMICH_URL=http://192.168.1.10:8080 \
-e IMMICH_PASSWORD='<your_password>' \
-e IMMICH_USERNAME=admin \
-e IMMICH_API_KEY=<your_api_key> \
-p 8090:8090 \
martabal/immich-exporter
```
@ -32,8 +35,7 @@ services:
container_name: immich-exporter
environment:
- IMMICH_URL=http://192.168.1.10:8080
- IMMICH_PASSWORD='<your_password>'
- IMMICH_USERNAME=admin
- IMMICH_API_KEY=<your_api_key>
ports:
- 8090:8090
restart: unless-stopped
@ -63,12 +65,24 @@ If you want to use an .env file, edit `.env.example` to match your setup, rename
| Parameters | Function |
| :-----: | ----- |
| `-p 8090` | Webservice port |
| `-e IMMICH_USERNAME` | Immich username |
| `-e IMMICH_PASSWORD` | Immich password |
| `-e IMMICH_BASE_URL` | Immich base URL |
| `-e IMMICH_API_KEY` | Immich API key |
| `-e EXPORTER_PORT` | qbittorrent export port (optional) | `8090` |
| `-e LOG_LEVEL` | App log level (`TRACE`, `DEBUG`, `INFO`, `WARN` and `ERROR`) | `INFO` |
### Arguments
| Arguments | Function |
| :-----: | ----- |
| -e | Use a .env file containing environment variables (.env file must be placed in the same directory) |
| -e | If qbittorrent-exporter detects a .env file in the same directory, the values in the .env will be used, `-e` forces the usage of environment variables |
### Setup
Add the target to your `scrape_configs` in your `prometheus.yml` file of your Prometheus instance.
```yaml
scrape_configs:
- job_name: 'immich'
static_configs:
- targets: [ '<your_ip_address>:8090' ]
```

10
go.mod
View file

@ -1,15 +1,19 @@
module immich-exporter
module immich-exp
go 1.20
require github.com/joho/godotenv v1.5.1
require (
github.com/joho/godotenv v1.5.1
github.com/prometheus/client_golang v1.14.0
github.com/sirupsen/logrus v1.6.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect

7
go.sum
View file

@ -52,6 +52,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -109,6 +110,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -138,6 +140,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@ -155,6 +158,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@ -185,11 +189,13 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -465,6 +471,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -1,6 +1,6 @@
{
"name": "immich-exporter",
"version": "0.1.2",
"version": "1.0.0",
"description": "exporter for immich",
"main": "src/main.go",
"scripts": {

View file

@ -1,60 +0,0 @@
package immich
import (
"encoding/json"
"fmt"
"immich-exporter/src/models"
"io/ioutil"
"log"
"net/http"
"strings"
)
func Auth() {
url := models.GetURL() + "/api/auth/login"
method := "POST"
payload := strings.NewReader(`{
"email": "` + models.GetUsername() + `",
"password": "` + models.Getpassword() + `"}`)
client := &http.Client{}
req, err := http.NewRequest(method, url, payload)
if err != nil {
fmt.Println(err)
return
} else {
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
res, err := client.Do(req)
if err != nil {
log.Println(err)
return
} else {
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Println(err)
return
}
if res.StatusCode == 400 {
log.Fatalln("Incorrect login")
} else {
var result models.StructLogin
if err := json.Unmarshal(body, &result); err != nil {
log.Println("Can not unmarshal JSON")
}
models.SetAccessToken(result.AccessToken)
}
}
}
}

View file

@ -3,7 +3,7 @@ package immich
import (
"encoding/json"
"fmt"
"immich-exporter/src/models"
"immich-exp/src/models"
"io/ioutil"
"log"
"net/http"
@ -25,9 +25,10 @@ func Allrequests(r *prometheus.Registry) {
func Analyze(r *prometheus.Registry) {
defer wg.Done()
allusers := make(chan func() (*models.StructAllUsers, error))
allusers := make(chan func() (*models.StructAllUsers, error))
serverinfo := make(chan func() (*models.StructServerInfo, error))
wg.Add(1)
go GetAllUsers(allusers)
res1, err := (<-allusers)()
@ -47,16 +48,7 @@ func Analyze(r *prometheus.Registry) {
func GetAllUsers(c chan func() (*models.StructAllUsers, error)) {
defer wg.Done()
resp, err := Apirequest("/api/user?isAll=true", "GET")
if err != nil {
if err.Error() == "403" {
log.Println("Cookie changed, try to reconnect ...")
Auth()
} else {
if models.GetPromptError() == false {
log.Println("Error : ", err)
}
}
} else {
if err == nil {
if models.GetPromptError() == true {
models.SetPromptError(false)
}
@ -71,25 +63,14 @@ func GetAllUsers(c chan func() (*models.StructAllUsers, error)) {
}
c <- (func() (*models.StructAllUsers, error) { return result, nil })
}
}
}
func ServerVersion(r *prometheus.Registry) {
defer wg.Done()
resp, err := Apirequest("/api/server-info/version", "GET")
if err != nil {
if err.Error() == "403" {
log.Println("Cookie changed, try to reconnect ...")
Auth()
} else {
if models.GetPromptError() == false {
log.Println("Error : ", err)
}
}
} else {
if err == nil {
if models.GetPromptError() == true {
models.SetPromptError(false)
}
@ -100,14 +81,12 @@ func ServerVersion(r *prometheus.Registry) {
var result models.StructServerVersion
if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer
log.Println("Can not unmarshal JSON")
log.Println("Can not unmarshal JSON for version")
}
SendBackMessageserverVersion(&result, r)
}
}
}
func ServerInfo(c chan func() (*models.StructServerInfo, error)) {
@ -116,7 +95,6 @@ func ServerInfo(c chan func() (*models.StructServerInfo, error)) {
if err != nil {
if err.Error() == "403" {
log.Println("Cookie changed, try to reconnect ...")
Auth()
} else {
if models.GetPromptError() == false {
log.Println("Error : ", err)
@ -134,23 +112,22 @@ func ServerInfo(c chan func() (*models.StructServerInfo, error)) {
result := new(models.StructServerInfo)
if err := json.Unmarshal(body, &result); err != nil { // Parse []byte to go struct pointer
log.Println("Can not unmarshal JSON")
log.Println("Can not unmarshal JSON for server infos")
}
c <- (func() (*models.StructServerInfo, error) { return result, nil })
}
}
}
func Apirequest(uri string, method string) (*http.Response, error) {
req, err := http.NewRequest(method, models.GetURL()+uri, nil)
req, err := http.NewRequest(method, models.Getbaseurl()+uri, nil)
if err != nil {
log.Fatalln("Error with url")
}
req.AddCookie(&http.Cookie{Name: "immich_access_token", Value: models.GetAccessToken()})
req.Header.Add("Accept", "application/json")
req.Header.Add("x-api-key", models.GetApiKey())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
@ -164,23 +141,16 @@ func Apirequest(uri string, method string) (*http.Response, error) {
return resp, err
} else {
models.SetPromptError(false)
if resp.StatusCode == 200 {
models.SetPromptError(false)
return resp, nil
} else {
err := fmt.Errorf("%d", resp.StatusCode)
if models.GetPromptError() == false {
models.SetPromptError(true)
log.Println("Error code", err.Error())
log.Println("Error code", err.Error(), " for ", models.Getbaseurl()+uri)
}
return resp, err
}
}
}

View file

@ -1,7 +1,7 @@
package immich
import (
"immich-exporter/src/models"
"immich-exp/src/models"
"strconv"
"github.com/prometheus/client_golang/prometheus"
@ -52,14 +52,14 @@ func SendBackMessagePreference(result *models.StructServerInfo, result2 *models.
r.MustRegister(user_photos)
total_photos.Add(float64((*result).Photos))
total_videos.Add(float64((*result).Videos))
total_usage.Add(float64((*result).UsageRaw))
total_usage.Add(float64((*result).Usage))
total_users.Add(float64(len((*result).UsageByUser)))
for i := 0; i < len((*result).UsageByUser); i++ {
var myuser = GetName((*result).UsageByUser[i].UserID, result2)
user_info.With(prometheus.Labels{"videos": strconv.Itoa((*result).UsageByUser[i].Videos), "photos": strconv.Itoa((*result).UsageByUser[i].Photos), "uid": (*result).UsageByUser[i].UserID, "usage": strconv.Itoa(int((*result).UsageByUser[i].UsageRaw)), "firstname": myuser.FirstName, "lastname": myuser.LastName}).Inc()
user_info.With(prometheus.Labels{"videos": strconv.Itoa((*result).UsageByUser[i].Videos), "photos": strconv.Itoa((*result).UsageByUser[i].Photos), "uid": (*result).UsageByUser[i].UserID, "usage": strconv.Itoa(int((*result).UsageByUser[i].Usage)), "firstname": myuser.FirstName, "lastname": myuser.LastName}).Inc()
user_photos.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].Photos))
user_usage.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].UsageRaw))
user_usage.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].Usage))
user_videos.With(prometheus.Labels{"uid": (*result).UsageByUser[i].UserID, "firstname": myuser.FirstName, "lastname": myuser.LastName}).Set(float64((*result).UsageByUser[i].Videos))
}

View file

@ -3,31 +3,39 @@ package main
import (
"encoding/json"
"flag"
"immich-exporter/src/immich"
"immich-exporter/src/models"
"io/ioutil"
"net/http"
"fmt"
"immich-exp/src/immich"
"immich-exp/src/models"
"net/http"
"strconv"
"strings"
"log"
"os"
"github.com/joho/godotenv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
)
func main() {
startup()
log.Println("Immich URL :", models.GetURL())
log.Println("username :", models.GetUsername())
log.Println("password :", models.Getpasswordmasked())
log.Println("Started")
const DEFAULTPORT = 8090
func main() {
loadenv()
projectinfo()
log.Info("Immich URL: ", models.Getbaseurl())
log.Info("Started")
http.HandleFunc("/metrics", metrics)
http.ListenAndServe(":8090", nil)
addr := ":" + strconv.Itoa(models.GetPort())
if models.GetPort() != DEFAULTPORT {
log.Info("Listening on port", models.GetPort())
}
http.ListenAndServe(addr, nil)
}
func metrics(w http.ResponseWriter, r *http.Request) {
log.Trace("New request")
registry := prometheus.NewRegistry()
immich.Allrequests(registry)
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
@ -35,80 +43,86 @@ func metrics(w http.ResponseWriter, r *http.Request) {
}
func startup() {
projectinfo()
var envfile bool
flag.BoolVar(&envfile, "e", false, "Use .env file")
flag.Parse()
if envfile {
useenvfile()
} else {
initenv()
}
immich.Auth()
}
func projectinfo() {
fileContent, err := os.Open("./package.json")
fileContent, err := os.ReadFile("./package.json")
if err != nil {
log.Fatal(err)
return
}
defer fileContent.Close()
byteResult, _ := ioutil.ReadAll(fileContent)
var res map[string]interface{}
json.Unmarshal([]byte(byteResult), &res)
log.Println("Author :", res["author"])
log.Println(res["name"], "version", res["version"])
err = json.Unmarshal(fileContent, &res)
if err != nil {
log.Fatal(err)
return
}
func useenvfile() {
myEnv, err := godotenv.Read()
fmt.Print(res["name"], " (version ", res["version"], ")\n")
fmt.Print("Author: ", res["author"], "\n")
fmt.Print("Using log level: ", log.GetLevel(), "\n")
}
username := myEnv["IMMICH_USERNAME"]
password := myEnv["IMMICH_PASSWORD"]
immich_url := myEnv["IMMICH_BASE_URL"]
if myEnv["IMMICH_USERNAME"] == "" {
log.Panic("Immich username is not set.")
func loadenv() {
var envfile bool
flag.BoolVar(&envfile, "e", false, "Use .env file")
flag.Parse()
_, err := os.Stat(".env")
if !os.IsNotExist(err) && !envfile {
err := godotenv.Load(".env")
if err != nil {
log.Panic("Error loading .env file:", err)
}
if myEnv["IMMICH_PASSWORD"] == "" {
log.Panic("Immich password is not set.")
// fmt.Println("Using .env file")
}
if myEnv["IMMICH_BASE_URL"] == "" {
log.Panic("IMMICH base_url is not set.")
}
models.Setuser(username, password, immich_url)
immichapikey := getEnv("IMMICH_API_KEY", "", false, "Immich API Key is not set", true)
immichURL := getEnv("IMMICH_BASE_URL", "http://localhost:8080", true, "Qbittorrent base_url is not set. Using default base_url", false)
exporterPort := getEnv("EXPORTER_PORT", strconv.Itoa(DEFAULTPORT), false, "", false)
num, err := strconv.Atoi(exporterPort)
if err != nil {
log.Fatal("Error loading .env file")
log.Panic("EXPORTER_PORT must be an integer")
}
log.Println("Using .env file")
if num < 0 || num > 65353 {
log.Panic("EXPORTER_PORT must be > 0 and < 65353")
}
func initenv() {
username := os.Getenv("IMMICH_USERNAME")
password := os.Getenv("IMMICH_PASSWORD")
immich_url := os.Getenv("IMMICH_BASE_URL")
if os.Getenv("IMMICH_USERNAME") == "" {
log.Panic("Immich username is not set.")
setLogLevel(getEnv("LOG_LEVEL", "INFO", false, "", false))
models.SetApp(num, false)
models.Setuser(immichURL, immichapikey)
}
if os.Getenv("IMMICH_PASSWORD") == "" {
log.Panic("Immich password is not set.")
func setLogLevel(logLevel string) {
logLevels := map[string]log.Level{
"TRACE": log.TraceLevel,
"DEBUG": log.DebugLevel,
"INFO": log.InfoLevel,
"WARN": log.WarnLevel,
"ERROR": log.ErrorLevel,
}
if os.Getenv("IMMICH_BASE_URL") == "" {
log.Panic("Immich base_url is not set.")
level, found := logLevels[strings.ToUpper(logLevel)]
if !found {
level = log.InfoLevel
}
models.Setuser(username, password, immich_url)
log.SetLevel(level)
log.SetFormatter(&log.TextFormatter{
DisableColors: false,
FullTimestamp: true,
})
}
func getEnv(key string, fallback string, printLog bool, logPrinted string, needed bool) string {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
if needed {
log.Panicln("Please set a value for", key)
}
if printLog {
log.Warn(logPrinted)
}
return fallback
}
return value
}

64
src/models/api.go Normal file
View file

@ -0,0 +1,64 @@
package models
import "time"
type StructLogin struct {
AccessToken string `json:"accessToken"`
UserID string `json:"userId"`
UserEmail string `json:"userEmail"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
IsAdmin bool `json:"isAdmin"`
ShouldChangePassword bool `json:"shouldChangePassword"`
}
type StructServerInfo struct {
Photos int `json:"photos"`
Videos int `json:"videos"`
Usage int64 `json:"usage"`
UsageByUser []struct {
UserID string `json:"userId"`
UserFirstName string `json:"userFirstName"`
UserLastName string `json:"userLastName"`
Photos int `json:"photos"`
Videos int `json:"videos"`
Usage int `json:"usage"`
} `json:"usageByUser"`
}
type StructDiskInfo struct {
DiskAvailable string `json:"diskAvailable"`
DiskSize string `json:"diskSize"`
DiskUse string `json:"diskUse"`
DiskAvailableRaw int64 `json:"diskAvailableRaw"`
DiskSizeRaw int64 `json:"diskSizeRaw"`
DiskUseRaw int64 `json:"diskUseRaw"`
DiskUsagePercentage float64 `json:"diskUsagePercentage"`
}
type StructServerVersion struct {
Major int `json:"major"`
Minor int `json:"minor"`
Patch int `json:"patch"`
}
type StructAllUsers []struct {
ID string `json:"id"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
CreatedAt time.Time `json:"createdAt"`
ProfileImagePath string `json:"profileImagePath"`
ShouldChangePassword bool `json:"shouldChangePassword"`
IsAdmin bool `json:"isAdmin"`
DeletedAt time.Time `json:"deletedAt"`
OauthID string `json:"oauthId"`
}
type StructCustomUser struct {
Email string
ID string
FirstName string
LastName string
IsAdmin bool
}

27
src/models/app.go Normal file
View file

@ -0,0 +1,27 @@
package models
type TypeAppConfig struct {
Port int
Error bool
}
var AppConfig TypeAppConfig
func SetApp(setport int, seterror bool) {
AppConfig = TypeAppConfig{
Port: setport,
Error: seterror,
}
}
func GetPort() int {
return AppConfig.Port
}
func SetPromptError(prompt bool) {
AppConfig.Error = prompt
}
func GetPromptError() bool {
return AppConfig.Error
}

View file

@ -1,11 +0,0 @@
package models
var myerr bool
func SetPromptError(prompt bool) {
myerr = prompt
}
func GetPromptError() bool {
return myerr
}

View file

@ -1,64 +1,22 @@
package models
import "time"
type StructLogin struct {
AccessToken string `json:"accessToken"`
UserID string `json:"userId"`
UserEmail string `json:"userEmail"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
IsAdmin bool `json:"isAdmin"`
ShouldChangePassword bool `json:"shouldChangePassword"`
type StructImmich struct {
APIKey string
URL string
}
type StructServerInfo struct {
Photos int `json:"photos"`
Videos int `json:"videos"`
UsageByUser []struct {
UserID string `json:"userId"`
Videos int `json:"videos"`
Photos int `json:"photos"`
UsageRaw int64 `json:"usageRaw"`
Usage string `json:"usage"`
} `json:"usageByUser"`
UsageRaw int64 `json:"usageRaw"`
Usage string `json:"usage"`
var myuser StructImmich
func Setuser(url string, apikey string) {
myuser.URL = url
myuser.APIKey = apikey
}
type StructDiskInfo struct {
DiskAvailable string `json:"diskAvailable"`
DiskSize string `json:"diskSize"`
DiskUse string `json:"diskUse"`
DiskAvailableRaw int64 `json:"diskAvailableRaw"`
DiskSizeRaw int64 `json:"diskSizeRaw"`
DiskUseRaw int64 `json:"diskUseRaw"`
DiskUsagePercentage float64 `json:"diskUsagePercentage"`
func Getbaseurl() string {
return myuser.URL
}
type StructServerVersion struct {
Major int `json:"major"`
Minor int `json:"minor"`
Patch int `json:"patch"`
}
type StructAllUsers []struct {
ID string `json:"id"`
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
CreatedAt time.Time `json:"createdAt"`
ProfileImagePath string `json:"profileImagePath"`
ShouldChangePassword bool `json:"shouldChangePassword"`
IsAdmin bool `json:"isAdmin"`
DeletedAt time.Time `json:"deletedAt"`
OauthID string `json:"oauthId"`
}
type StructCustomUser struct {
Email string
ID string
FirstName string
LastName string
IsAdmin bool
func GetApiKey() string {
return myuser.APIKey
}

View file

@ -1,47 +0,0 @@
package models
type StructImmich struct {
Username string
Password string
URL string
AccessToken string
}
var myuser StructImmich
func Getuser() (string, string) {
return myuser.Username, myuser.Password
}
func Setuser(username string, password string, url string) {
myuser.Username = username
myuser.Password = password
myuser.URL = url
}
func GetUsername() string {
return myuser.Username
}
func Getpassword() string {
return myuser.Password
}
func Getpasswordmasked() string {
hide := ""
for i := 0; i < len(myuser.Password); i++ {
hide += "*"
}
return hide
}
func SetAccessToken(accessToken string) {
myuser.AccessToken = accessToken
}
func GetAccessToken() string {
return myuser.AccessToken
}
func GetURL() string {
return myuser.URL
}