commit ea8fff629c67b1b47d0ffec2f97ce461dada0b61 Author: Daniel Dada Date: Tue Feb 24 16:34:39 2026 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9440a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/data +/config/media \ No newline at end of file diff --git a/config/monitoring/glance/glance.yml b/config/monitoring/glance/glance.yml new file mode 100644 index 0000000..3d022b1 --- /dev/null +++ b/config/monitoring/glance/glance.yml @@ -0,0 +1,97 @@ +# Glance – main config +# See: https://github.com/glanceapp/glance/blob/main/docs/configuration.md + +server: + assets-path: /app/assets + proxied: true + +theme: + custom-css-file: /assets/user.css + +pages: + - name: Home + columns: + - size: small + widgets: + - type: clock + - type: calendar + first-day-of-week: monday + - type: releases + cache: 1d + repositories: + - glanceapp/glance + - immich-app/immich + - size: full + widgets: + - type: hacker-news + limit: 15 + collapse-after: 5 + - type: lobsters + sort-by: hot + limit: 15 + collapse-after: 5 + - type: reddit + subreddit: unixporn + limit: 15 + collapse-after: 5 + - size: small + widgets: + - type: bookmarks + title: Services + groups: + - same-tab: true + links: + - title: Jellyfin + url: https://jellyfin.home + icon: mdi:television-play + - title: Seer + url: https://jellyseerr.home + icon: mdi:movie-search + - title: Radarr + url: https://radarr.home + icon: mdi:film + - title: Sonarr + url: https://sonarr.home + icon: mdi:television + - title: Lidarr + url: https://lidarr.home + icon: mdi:music + - title: Prowlarr + url: https://prowlarr.home + icon: mdi:radar + - title: qBittorrent + url: https://qbit.home + icon: mdi:download + - title: Portainer + url: https://portainer.home + icon: mdi:docker + - title: Immich + url: https://photo.home + icon: mdi:image-multiple + - title: Navidrome + url: https://music.home + icon: mdi:music-box-multiple + - title: CopyParty + url: https://copyparty.home + icon: mdi:folder-multiple + - title: Gitea + url: https://git.home + icon: mdi:git + - title: Grafana + url: https://grafana.home + icon: mdi:chart-line + - name: Media + columns: + - size: full + widgets: + - type: group + widgets: + - $include: widgets/arr-radarr-upcoming.yml + - $include: widgets/arr-radarr-recent.yml + - $include: widgets/arr-radarr-missing.yml + - $include: widgets/arr-sonarr-upcoming.yml + - $include: widgets/arr-sonarr-recent.yml + - $include: widgets/arr-sonarr-missing.yml + - $include: widgets/monitor-media.yml + - $include: pages/services.yml + - $include: pages/gaming.yml diff --git a/config/monitoring/glance/pages/gaming.yml b/config/monitoring/glance/pages/gaming.yml new file mode 100644 index 0000000..5163a47 --- /dev/null +++ b/config/monitoring/glance/pages/gaming.yml @@ -0,0 +1,39 @@ +- name: Gaming + columns: + - size: small + widgets: + - type: twitch-top-games + limit: 20 + collapse-after: 13 + exclude: + - just-chatting + - pools-hot-tubs-and-beaches + - music + - art + - asmr + + - size: full + widgets: + - type: group + widgets: + - type: reddit + show-thumbnails: true + subreddit: pcgaming + - type: reddit + subreddit: games + + - type: videos + style: grid-cards + collapse-after-rows: 3 + channels: + - UCNvzD7Z-g64bPXxGzaQaa4g # gameranx + - UCZ7AeeVbyslLM_8-nVy2B8Q # Skill Up + - UCHDxYLv8iovIbhrfl16CNyg # GameLinked + - UC9PBzalIcEQCsiIkq36PyUA # Digital Foundry + + - size: small + widgets: + - type: reddit + subreddit: gamingnews + limit: 7 + style: vertical-cards \ No newline at end of file diff --git a/config/monitoring/glance/pages/home.yml b/config/monitoring/glance/pages/home.yml new file mode 100644 index 0000000..98db1d9 --- /dev/null +++ b/config/monitoring/glance/pages/home.yml @@ -0,0 +1,16 @@ +# Home: clock, calendar, quick links to all services +name: Home +columns: + - size: small + widgets: + - type: clock + - type: calendar + first-day-of-week: monday + - $include: ../widgets/bookmarks-services.yml + - size: full + widgets: + - type: releases + cache: 1d + repositories: + - glanceapp/glance + - immich-app/immich diff --git a/config/monitoring/glance/pages/media.yml b/config/monitoring/glance/pages/media.yml new file mode 100644 index 0000000..a09dcbc --- /dev/null +++ b/config/monitoring/glance/pages/media.yml @@ -0,0 +1,15 @@ +# Media: Jellyfin (next up + latest), *arr wanted, status +name: Media +columns: + - size: full + widgets: + - type: group + widgets: + - $include: ../widgets/arr-radarr-upcoming.yml + - $include: ../widgets/arr-radarr-recent.yml + - $include: ../widgets/arr-radarr-missing.yml + - $include: ../widgets/arr-sonarr-upcoming.yml + - $include: ../widgets/arr-sonarr-recent.yml + - $include: ../widgets/arr-sonarr-missing.yml + - $include: ../widgets/jellyfin.yml + - $include: ../widgets/monitor-media.yml diff --git a/config/monitoring/glance/pages/services.yml b/config/monitoring/glance/pages/services.yml new file mode 100644 index 0000000..efad147 --- /dev/null +++ b/config/monitoring/glance/pages/services.yml @@ -0,0 +1,83 @@ +# Services: status monitor, Docker containers, Portainer +- name: Services + columns: + - size: full + widgets: + - type: server-stats + title: Server + servers: + - type: local + name: Services + mountpoints: + "/": + name: Root + "/srv/media": + name: Media + "/backup": + name: Backup + - type: monitor + title: Service status + sites: + - title: Jellyfin + url: https://jellyfin.home + icon: mdi:television-play + - title: Seer + url: https://jellyseerr.home + icon: mdi:movie-search + - title: Radarr + url: https://radarr.home + icon: mdi:film + - title: Sonarr + url: https://sonarr.home + icon: mdi:television + - title: Lidarr + url: https://lidarr.home + icon: mdi:music + - title: Prowlarr + url: https://prowlarr.home + icon: mdi:radar + - title: qBittorrent + url: https://qbit.home + icon: mdi:download + - title: Portainer + url: https://portainer.home + icon: mdi:docker + - title: Immich + url: https://photo.home + icon: mdi:image-multiple + - title: Navidrome + url: https://music.home + icon: mdi:music-box-multiple + - title: CopyParty + url: https://copyparty.home + icon: mdi:folder-multiple + - title: Gitea + url: https://git.home + icon: mdi:git + - title: Grafana + url: https://grafana.home + icon: mdi:chart-line + - type: docker-containers + title: Docker + show-stats: true + - type: custom-api + title: Portainer + title-url: https://portainer.home + cache: 1h + url: ${PORTAINER_URL}/api/endpoints/${PORTAINER_ENDPOINT_ID} + headers: + X-API-Key: ${PORTAINER_API_KEY} + template: | + +
+ {{ .JSON.String "Name" }} + Containers: {{ .JSON.Int "Snapshots.0.ContainerCount" }} + Volumes: {{ .JSON.Int "Snapshots.0.VolumeCount" }} + Networks: {{ .JSON.Int "Snapshots.0.DockerSnapshotRaw.Networks.#" }} +
+ diff --git a/config/monitoring/glance/widgets/arr-radarr-missing.yml b/config/monitoring/glance/widgets/arr-radarr-missing.yml new file mode 100644 index 0000000..5695d70 --- /dev/null +++ b/config/monitoring/glance/widgets/arr-radarr-missing.yml @@ -0,0 +1,450 @@ +# *arr community widget: Radarr wanted movies +- type: custom-api + title: Wanted Movies + title-url: ${RADARR_URL} + cache: 30m + options: + service: radarr + type: missing + size: medium + collapse-after: 5 + show-grabbed: false + interval: 20 + api-base-url: ${RADARR_API_URL} + key: ${RADARR_KEY} + url: ${RADARR_URL} + template: | + {{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} + {{ $showGrabbed := .Options.BoolOr "show-grabbed" false }} + {{ $sortTime := .Options.StringOr "sort-time" "desc" }} + {{ $coverProxy := .Options.StringOr "cover-proxy" "" }} + {{ $size := .Options.StringOr "size" "medium" }} + {{ $service := .Options.StringOr "service" "" }} + {{ $type := .Options.StringOr "type" "" }} + {{ $intervalH := .Options.IntOr "interval" 0 | mul 24 }} + + {{ $now := now | formatTime "2006-01-02T15:04:05" }} + {{ $posInterval := (offsetNow (printf "+%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + {{ $negInterval := (offsetNow (printf "-%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + + {{ $apiBaseUrl := .Options.StringOr "api-base-url" "" }} + {{ $key := .Options.StringOr "key" "" }} + {{ $url := .Options.StringOr "url" $apiBaseUrl }} + + {{ if or (and (ne $service "sonarr") (ne $service "radarr") (ne $service "lidarr")) + (and (ne $type "upcoming") (ne $type "recent") (ne $type "missing")) + (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

+ Some options are not set or malformed + + + + + +
service{{ $service }}must be sonarr, radarr, or lidarr
type{{ $type }}must be upcoming, recent, or missing
api-base-url{{ $apiBaseUrl }}should include http(s):// and port if needed
key{{ $key }}
+

+ {{ else }} + {{ $requestUrl := "" }} + + {{ if eq $service "sonarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeSeries=true&includeEpisode=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50&includeSeries=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?includeSeries=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "radarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeMovie=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "lidarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v1/history/since?includeArtist=true&includeAlbum=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v1/wanted/missing?page=1&pageSize=50&includeArtist=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v1/calendar?includeArtist=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + {{ end }} + + {{ $data := newRequest $requestUrl + | withHeader "Accept" "application/json" + | withHeader "X-Api-Key" $key + | getResponse }} + + + {{ end }} + diff --git a/config/monitoring/glance/widgets/arr-radarr-recent.yml b/config/monitoring/glance/widgets/arr-radarr-recent.yml new file mode 100644 index 0000000..f7d4a8e --- /dev/null +++ b/config/monitoring/glance/widgets/arr-radarr-recent.yml @@ -0,0 +1,449 @@ +# *arr community widget: Radarr recent movies +- type: custom-api + title: Recent Movies + title-url: ${RADARR_URL} + cache: 30m + options: + service: radarr + type: recent + size: medium + collapse-after: 5 + show-grabbed: false + interval: 20 + api-base-url: ${RADARR_API_URL} + key: ${RADARR_KEY} + url: ${RADARR_URL} + template: | + {{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} + {{ $showGrabbed := .Options.BoolOr "show-grabbed" false }} + {{ $sortTime := .Options.StringOr "sort-time" "desc" }} + {{ $coverProxy := .Options.StringOr "cover-proxy" "" }} + {{ $size := .Options.StringOr "size" "medium" }} + {{ $service := .Options.StringOr "service" "" }} + {{ $type := .Options.StringOr "type" "" }} + {{ $intervalH := .Options.IntOr "interval" 0 | mul 24 }} + + {{ $now := now | formatTime "2006-01-02T15:04:05" }} + {{ $posInterval := (offsetNow (printf "+%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + {{ $negInterval := (offsetNow (printf "-%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + + {{ $apiBaseUrl := .Options.StringOr "api-base-url" "" }} + {{ $key := .Options.StringOr "key" "" }} + {{ $url := .Options.StringOr "url" $apiBaseUrl }} + + {{ if or (and (ne $service "sonarr") (ne $service "radarr") (ne $service "lidarr")) + (and (ne $type "upcoming") (ne $type "recent") (ne $type "missing")) + (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

+ Some options are not set or malformed + + + + + +
service{{ $service }}must be sonarr, radarr, or lidarr
type{{ $type }}must be upcoming, recent, or missing
api-base-url{{ $apiBaseUrl }}should include http(s):// and port if needed
key{{ $key }}
+

+ {{ else }} + {{ $requestUrl := "" }} + + {{ if eq $service "sonarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeSeries=true&includeEpisode=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50&includeSeries=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?includeSeries=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "radarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeMovie=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "lidarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v1/history/since?includeArtist=true&includeAlbum=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v1/wanted/missing?page=1&pageSize=50&includeArtist=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v1/calendar?includeArtist=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + {{ end }} + + {{ $data := newRequest $requestUrl + | withHeader "Accept" "application/json" + | withHeader "X-Api-Key" $key + | getResponse }} + + + {{ end }} diff --git a/config/monitoring/glance/widgets/arr-radarr-upcoming.yml b/config/monitoring/glance/widgets/arr-radarr-upcoming.yml new file mode 100644 index 0000000..b45f318 --- /dev/null +++ b/config/monitoring/glance/widgets/arr-radarr-upcoming.yml @@ -0,0 +1,449 @@ +# *arr community widget: Radarr upcoming movies +- type: custom-api + title: Upcoming Movies + title-url: ${RADARR_URL} + cache: 30m + options: + service: radarr + type: upcoming + size: medium + collapse-after: 5 + show-grabbed: false + interval: 20 + api-base-url: ${RADARR_API_URL} + key: ${RADARR_KEY} + url: ${RADARR_URL} + template: | + {{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} + {{ $showGrabbed := .Options.BoolOr "show-grabbed" false }} + {{ $sortTime := .Options.StringOr "sort-time" "desc" }} + {{ $coverProxy := .Options.StringOr "cover-proxy" "" }} + {{ $size := .Options.StringOr "size" "medium" }} + {{ $service := .Options.StringOr "service" "" }} + {{ $type := .Options.StringOr "type" "" }} + {{ $intervalH := .Options.IntOr "interval" 0 | mul 24 }} + + {{ $now := now | formatTime "2006-01-02T15:04:05" }} + {{ $posInterval := (offsetNow (printf "+%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + {{ $negInterval := (offsetNow (printf "-%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + + {{ $apiBaseUrl := .Options.StringOr "api-base-url" "" }} + {{ $key := .Options.StringOr "key" "" }} + {{ $url := .Options.StringOr "url" $apiBaseUrl }} + + {{ if or (and (ne $service "sonarr") (ne $service "radarr") (ne $service "lidarr")) + (and (ne $type "upcoming") (ne $type "recent") (ne $type "missing")) + (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

+ Some options are not set or malformed + + + + + +
service{{ $service }}must be sonarr, radarr, or lidarr
type{{ $type }}must be upcoming, recent, or missing
api-base-url{{ $apiBaseUrl }}should include http(s):// and port if needed
key{{ $key }}
+

+ {{ else }} + {{ $requestUrl := "" }} + + {{ if eq $service "sonarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeSeries=true&includeEpisode=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50&includeSeries=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?includeSeries=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "radarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeMovie=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "lidarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v1/history/since?includeArtist=true&includeAlbum=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v1/wanted/missing?page=1&pageSize=50&includeArtist=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v1/calendar?includeArtist=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + {{ end }} + + {{ $data := newRequest $requestUrl + | withHeader "Accept" "application/json" + | withHeader "X-Api-Key" $key + | getResponse }} + + + {{ end }} diff --git a/config/monitoring/glance/widgets/arr-sonarr-missing.yml b/config/monitoring/glance/widgets/arr-sonarr-missing.yml new file mode 100644 index 0000000..188ef75 --- /dev/null +++ b/config/monitoring/glance/widgets/arr-sonarr-missing.yml @@ -0,0 +1,450 @@ +# *arr community widget: Sonarr wanted episodes +- type: custom-api + title: Wanted Episodes + title-url: ${SONARR_URL} + cache: 30m + options: + service: sonarr + type: missing + size: medium + collapse-after: 5 + show-grabbed: false + interval: 20 + api-base-url: ${SONARR_API_URL} + key: ${SONARR_KEY} + url: ${SONARR_URL} + template: | + {{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} + {{ $showGrabbed := .Options.BoolOr "show-grabbed" false }} + {{ $sortTime := .Options.StringOr "sort-time" "desc" }} + {{ $coverProxy := .Options.StringOr "cover-proxy" "" }} + {{ $size := .Options.StringOr "size" "medium" }} + {{ $service := .Options.StringOr "service" "" }} + {{ $type := .Options.StringOr "type" "" }} + {{ $intervalH := .Options.IntOr "interval" 0 | mul 24 }} + + {{ $now := now | formatTime "2006-01-02T15:04:05" }} + {{ $posInterval := (offsetNow (printf "+%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + {{ $negInterval := (offsetNow (printf "-%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + + {{ $apiBaseUrl := .Options.StringOr "api-base-url" "" }} + {{ $key := .Options.StringOr "key" "" }} + {{ $url := .Options.StringOr "url" $apiBaseUrl }} + + {{ if or (and (ne $service "sonarr") (ne $service "radarr") (ne $service "lidarr")) + (and (ne $type "upcoming") (ne $type "recent") (ne $type "missing")) + (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

+ Some options are not set or malformed + + + + + +
service{{ $service }}must be sonarr, radarr, or lidarr
type{{ $type }}must be upcoming, recent, or missing
api-base-url{{ $apiBaseUrl }}should include http(s):// and port if needed
key{{ $key }}
+

+ {{ else }} + {{ $requestUrl := "" }} + + {{ if eq $service "sonarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeSeries=true&includeEpisode=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50&includeSeries=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?includeSeries=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "radarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeMovie=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "lidarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v1/history/since?includeArtist=true&includeAlbum=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v1/wanted/missing?page=1&pageSize=50&includeArtist=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v1/calendar?includeArtist=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + {{ end }} + + {{ $data := newRequest $requestUrl + | withHeader "Accept" "application/json" + | withHeader "X-Api-Key" $key + | getResponse }} + + + {{ end }} + diff --git a/config/monitoring/glance/widgets/arr-sonarr-recent.yml b/config/monitoring/glance/widgets/arr-sonarr-recent.yml new file mode 100644 index 0000000..e866c99 --- /dev/null +++ b/config/monitoring/glance/widgets/arr-sonarr-recent.yml @@ -0,0 +1,450 @@ +# *arr community widget: Sonarr recent episodes +- type: custom-api + title: Recent Episodes + title-url: ${SONARR_URL} + cache: 30m + options: + service: sonarr + type: recent + size: medium + collapse-after: 5 + show-grabbed: false + interval: 20 + api-base-url: ${SONARR_API_URL} + key: ${SONARR_KEY} + url: ${SONARR_URL} + template: | + {{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} + {{ $showGrabbed := .Options.BoolOr "show-grabbed" false }} + {{ $sortTime := .Options.StringOr "sort-time" "desc" }} + {{ $coverProxy := .Options.StringOr "cover-proxy" "" }} + {{ $size := .Options.StringOr "size" "medium" }} + {{ $service := .Options.StringOr "service" "" }} + {{ $type := .Options.StringOr "type" "" }} + {{ $intervalH := .Options.IntOr "interval" 0 | mul 24 }} + + {{ $now := now | formatTime "2006-01-02T15:04:05" }} + {{ $posInterval := (offsetNow (printf "+%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + {{ $negInterval := (offsetNow (printf "-%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + + {{ $apiBaseUrl := .Options.StringOr "api-base-url" "" }} + {{ $key := .Options.StringOr "key" "" }} + {{ $url := .Options.StringOr "url" $apiBaseUrl }} + + {{ if or (and (ne $service "sonarr") (ne $service "radarr") (ne $service "lidarr")) + (and (ne $type "upcoming") (ne $type "recent") (ne $type "missing")) + (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

+ Some options are not set or malformed + + + + + +
service{{ $service }}must be sonarr, radarr, or lidarr
type{{ $type }}must be upcoming, recent, or missing
api-base-url{{ $apiBaseUrl }}should include http(s):// and port if needed
key{{ $key }}
+

+ {{ else }} + {{ $requestUrl := "" }} + + {{ if eq $service "sonarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeSeries=true&includeEpisode=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50&includeSeries=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?includeSeries=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "radarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeMovie=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "lidarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v1/history/since?includeArtist=true&includeAlbum=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v1/wanted/missing?page=1&pageSize=50&includeArtist=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v1/calendar?includeArtist=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + {{ end }} + + {{ $data := newRequest $requestUrl + | withHeader "Accept" "application/json" + | withHeader "X-Api-Key" $key + | getResponse }} + + + {{ end }} + diff --git a/config/monitoring/glance/widgets/arr-sonarr-upcoming.yml b/config/monitoring/glance/widgets/arr-sonarr-upcoming.yml new file mode 100644 index 0000000..952f39f --- /dev/null +++ b/config/monitoring/glance/widgets/arr-sonarr-upcoming.yml @@ -0,0 +1,450 @@ +# *arr community widget: Sonarr upcoming episodes +- type: custom-api + title: Upcoming Episodes + title-url: ${SONARR_URL} + cache: 30m + options: + service: sonarr + type: upcoming + size: medium + collapse-after: 5 + show-grabbed: false + interval: 20 + api-base-url: ${SONARR_API_URL} + key: ${SONARR_KEY} + url: ${SONARR_URL} + template: | + {{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} + {{ $showGrabbed := .Options.BoolOr "show-grabbed" false }} + {{ $sortTime := .Options.StringOr "sort-time" "desc" }} + {{ $coverProxy := .Options.StringOr "cover-proxy" "" }} + {{ $size := .Options.StringOr "size" "medium" }} + {{ $service := .Options.StringOr "service" "" }} + {{ $type := .Options.StringOr "type" "" }} + {{ $intervalH := .Options.IntOr "interval" 0 | mul 24 }} + + {{ $now := now | formatTime "2006-01-02T15:04:05" }} + {{ $posInterval := (offsetNow (printf "+%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + {{ $negInterval := (offsetNow (printf "-%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + + {{ $apiBaseUrl := .Options.StringOr "api-base-url" "" }} + {{ $key := .Options.StringOr "key" "" }} + {{ $url := .Options.StringOr "url" $apiBaseUrl }} + + {{ if or (and (ne $service "sonarr") (ne $service "radarr") (ne $service "lidarr")) + (and (ne $type "upcoming") (ne $type "recent") (ne $type "missing")) + (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

+ Some options are not set or malformed + + + + + +
service{{ $service }}must be sonarr, radarr, or lidarr
type{{ $type }}must be upcoming, recent, or missing
api-base-url{{ $apiBaseUrl }}should include http(s):// and port if needed
key{{ $key }}
+

+ {{ else }} + {{ $requestUrl := "" }} + + {{ if eq $service "sonarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeSeries=true&includeEpisode=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50&includeSeries=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?includeSeries=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "radarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeMovie=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "lidarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v1/history/since?includeArtist=true&includeAlbum=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v1/wanted/missing?page=1&pageSize=50&includeArtist=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v1/calendar?includeArtist=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + {{ end }} + + {{ $data := newRequest $requestUrl + | withHeader "Accept" "application/json" + | withHeader "X-Api-Key" $key + | getResponse }} + + + {{ end }} + diff --git a/config/monitoring/glance/widgets/arr-template.txt b/config/monitoring/glance/widgets/arr-template.txt new file mode 100644 index 0000000..ed29533 --- /dev/null +++ b/config/monitoring/glance/widgets/arr-template.txt @@ -0,0 +1,433 @@ +{{ $collapseAfter := .Options.IntOr "collapse-after" 5 }} +{{ $showGrabbed := .Options.BoolOr "show-grabbed" false }} +{{ $sortTime := .Options.StringOr "sort-time" "desc" }} +{{ $coverProxy := .Options.StringOr "cover-proxy" "" }} +{{ $size := .Options.StringOr "size" "medium" }} +{{ $service := .Options.StringOr "service" "" }} +{{ $type := .Options.StringOr "type" "" }} +{{ $intervalH := .Options.IntOr "interval" 0 | mul 24 }} + +{{ $now := now | formatTime "2006-01-02T15:04:05" }} +{{ $posInterval := (offsetNow (printf "+%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} +{{ $negInterval := (offsetNow (printf "-%dh" $intervalH)) | formatTime "2006-01-02T15:04:05" }} + +{{ $apiBaseUrl := .Options.StringOr "api-base-url" "" }} +{{ $key := .Options.StringOr "key" "" }} +{{ $url := .Options.StringOr "url" $apiBaseUrl }} + +{{ if or (and (ne $service "sonarr") (ne $service "radarr") (ne $service "lidarr")) + (and (ne $type "upcoming") (ne $type "recent") (ne $type "missing")) + (eq $apiBaseUrl "") (eq $key "") (eq $url "") }} +
+
ERROR
+ + + +
+

+ Some options are not set or malformed + + + + + +
service{{ $service }}must be sonarr, radarr, or lidarr
type{{ $type }}must be upcoming, recent, or missing
api-base-url{{ $apiBaseUrl }}should include http(s):// and port if needed
key{{ $key }}
+

+{{ else }} + {{ $requestUrl := "" }} + + {{ if eq $service "sonarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeSeries=true&includeEpisode=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50&includeSeries=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?includeSeries=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "radarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v3/history/since?includeMovie=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v3/wanted/missing?page=1&pageSize=50" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v3/calendar?start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + + {{ else if eq $service "lidarr" }} + {{ if eq $type "recent" }} + {{ $requestUrl = printf "%s/api/v1/history/since?includeArtist=true&includeAlbum=true&eventType=grabbed&date=%s" $apiBaseUrl $negInterval }} + {{ else if eq $type "missing" }} + {{ $requestUrl = printf "%s/api/v1/wanted/missing?page=1&pageSize=50&includeArtist=true" $apiBaseUrl }} + {{ else if eq $type "upcoming" }} + {{ $requestUrl = printf "%s/api/v1/calendar?includeArtist=true&start=%s&end=%s" $apiBaseUrl $now $posInterval }} + {{ end }} + {{ end }} + + {{ $data := newRequest $requestUrl + | withHeader "Accept" "application/json" + | withHeader "X-Api-Key" $key + | getResponse }} + + +{{ end }} diff --git a/config/monitoring/glance/widgets/monitor-media.yml b/config/monitoring/glance/widgets/monitor-media.yml new file mode 100644 index 0000000..0a35ee6 --- /dev/null +++ b/config/monitoring/glance/widgets/monitor-media.yml @@ -0,0 +1,27 @@ +- type: monitor + title: Media + sites: + - title: Jellyfin + url: https://jellyfin.home + icon: mdi:television-play + - title: Jellyseerr + url: https://jellyseerr.home + icon: mdi:movie-search + - title: Radarr + url: https://radarr.home + icon: mdi:film + - title: Sonarr + url: https://sonarr.home + icon: mdi:television + - title: Lidarr + url: https://lidarr.home + icon: mdi:music + - title: Prowlarr + url: https://prowlarr.home + icon: mdi:radar + - title: qBittorrent + url: https://qbit.home + icon: mdi:download + - title: TorrServe + url: https://torrserve.home + icon: mdi:server-network diff --git a/config/monitoring/grafana/provisioning/dashboards/dashboards.yaml b/config/monitoring/grafana/provisioning/dashboards/dashboards.yaml new file mode 100644 index 0000000..4d8b6a8 --- /dev/null +++ b/config/monitoring/grafana/provisioning/dashboards/dashboards.yaml @@ -0,0 +1,10 @@ +apiVersion: 1 + +providers: + - name: homelab + folder: Homelab + type: file + disableDeletion: false + editable: true + options: + path: /etc/grafana/provisioning/dashboards/json diff --git a/config/monitoring/grafana/provisioning/datasources/datasources.yaml b/config/monitoring/grafana/provisioning/datasources/datasources.yaml new file mode 100644 index 0000000..789d5d4 --- /dev/null +++ b/config/monitoring/grafana/provisioning/datasources/datasources.yaml @@ -0,0 +1,16 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: false + editable: true + + - name: Loki + type: loki + access: proxy + url: http://loki:3100 + isDefault: true + editable: true diff --git a/config/monitoring/loki-config.yaml b/config/monitoring/loki-config.yaml new file mode 100644 index 0000000..7aa27f1 --- /dev/null +++ b/config/monitoring/loki-config.yaml @@ -0,0 +1,49 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + log_level: info + +ingester: + wal: + dir: /loki/wal + lifecycler: + ring: + kvstore: + store: inmemory + replication_factor: 1 + chunk_idle_period: 5m + chunk_target_size: 1048576 + max_transfer_retries: 0 + +schema_config: + configs: + - from: 2020-10-24 + store: boltdb + object_store: filesystem + schema: v11 + index: + prefix: index_ + period: 168h + +storage_config: + boltdb: + directory: /loki/index + filesystem: + directory: /loki/chunks + +limits_config: + enforce_metric_name: false + reject_old_samples: true + reject_old_samples_max_age: 168h + split_queries_by_interval: 15m + +querier: + max_outstanding_per_tenant: 4096 + +chunk_store_config: + max_look_back_period: 0s + +table_manager: + retention_deletes_enabled: true + retention_period: 168h diff --git a/config/monitoring/prometheus.yml b/config/monitoring/prometheus.yml new file mode 100644 index 0000000..3e97ca9 --- /dev/null +++ b/config/monitoring/prometheus.yml @@ -0,0 +1,24 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + - job_name: "node" + static_configs: + - targets: ["192.168.1.70:9100"] + + - job_name: "cadvisor" + static_configs: + - targets: ["cadvisor:8080"] + + - job_name: "prometheus" + static_configs: + - targets: ["localhost:9090"] + + - job_name: "caddy" + scheme: https + metrics_path: /metrics + tls_config: + insecure_skip_verify: true + static_configs: + - targets: ["metrics.home:443"] diff --git a/config/monitoring/promtail-config.yaml b/config/monitoring/promtail-config.yaml new file mode 100644 index 0000000..764f6b3 --- /dev/null +++ b/config/monitoring/promtail-config.yaml @@ -0,0 +1,48 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://loki:3100/loki/api/v1/push + +scrape_configs: + - job_name: varlogs + static_configs: + - targets: [localhost] + labels: + job: varlogs + host: cyberdeck + __path__: /var/log/**/*.log + + - job_name: docker + static_configs: + - targets: [localhost] + labels: + job: docker + host: cyberdeck + __path__: /var/lib/docker/containers/*/*-json.log + pipeline_stages: + - docker: {} + relabel_configs: + - source_labels: [__path__] + regex: "/var/lib/docker/containers/([^/]+)/.*" + target_label: container + replacement: "$1" + + - job_name: journald + journal: + path: /var/log/journal + max_age: 12h + labels: + job: journald + host: cyberdeck + relabel_configs: + - source_labels: [__journal__systemd_unit] + target_label: unit + - source_labels: [__journal__hostname] + target_label: host + - source_labels: [__journal__priority_keyword] + target_label: level diff --git a/config/storage/copyparty/copyparty.conf b/config/storage/copyparty/copyparty.conf new file mode 100644 index 0000000..bede08e --- /dev/null +++ b/config/storage/copyparty/copyparty.conf @@ -0,0 +1,14 @@ +[global] + i: 0.0.0.0 + rproxy: -1 + xff-src: 172.20.0.0/16 + +[accounts] + dan: 44435777 + +[/] +/srv/media/share + accs: + r: * # everyone gets read-access, but + rw: dan # the user "ed" gets read-write + d: dan diff --git a/config/storage/copyparty/initcfg b/config/storage/copyparty/initcfg new file mode 100644 index 0000000..42ec2da --- /dev/null +++ b/config/storage/copyparty/initcfg @@ -0,0 +1 @@ +# Override image default to avoid duplicate [/] volume (see copyparty.conf for volume definition). diff --git a/stacks/media/arr.yaml b/stacks/media/arr.yaml new file mode 100644 index 0000000..e95e955 --- /dev/null +++ b/stacks/media/arr.yaml @@ -0,0 +1,83 @@ +# When *arr apps connect to Prowlarr, use HTTP and Docker service names: +# Lidarr: http://lidarr:8686 Radarr: http://radarr:7878 +# Sonarr: http://sonarr:8989 Prowlarr: http://prowlarr:9696 +services: + sonarr: + image: lscr.io/linuxserver/sonarr:latest + container_name: sonarr + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Moscow + ports: + - "8989:8989" + volumes: + - /srv/homelab/config/media/sonarr:/config + - /srv/media:/srv/media + - /srv/media/downloads:/downloads + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + radarr: + image: lscr.io/linuxserver/radarr:latest + container_name: radarr + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Moscow + ports: + - "7878:7878" + volumes: + - /srv/homelab/config/media/radarr:/config + - /srv/media:/srv/media + - /srv/media/downloads:/downloads + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + lidarr: + image: lscr.io/linuxserver/lidarr:latest + container_name: lidarr + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Moscow + ports: + - "8686:8686" + volumes: + - /srv/homelab/config/media/lidarr:/config + - /srv/media:/srv/media + - /srv/media/downloads:/downloads + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + prowlarr: + image: lscr.io/linuxserver/prowlarr:latest + container_name: prowlarr + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Moscow + ports: + - "9696:9696" + volumes: + - /srv/homelab/config/media/prowlarr:/config + - /srv/media:/srv/media + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + diff --git a/stacks/media/compose.yaml b/stacks/media/compose.yaml new file mode 100644 index 0000000..284672e --- /dev/null +++ b/stacks/media/compose.yaml @@ -0,0 +1,9 @@ +name: media + +include: + - ./arr.yaml + - ./qbittorrent.yaml + - ./jellyfin.yaml + - ./seer.yaml + - ./navidrome.yaml + - ./immich.yaml diff --git a/stacks/media/immich.env b/stacks/media/immich.env new file mode 100644 index 0000000..7f89982 --- /dev/null +++ b/stacks/media/immich.env @@ -0,0 +1,10 @@ +TZ=Europe/Moscow +IMMICH_VERSION=v2 +DB_HOSTNAME=immich-postgres +REDIS_HOSTNAME=immich-redis +DB_PASSWORD=change_me +DB_USERNAME=postgres +DB_DATABASE_NAME=immich +POSTGRES_PASSWORD=change_me +POSTGRES_USER=postgres +POSTGRES_DB=immich diff --git a/stacks/media/immich.yaml b/stacks/media/immich.yaml new file mode 100644 index 0000000..5d95ad4 --- /dev/null +++ b/stacks/media/immich.yaml @@ -0,0 +1,67 @@ +services: + immich-server: + container_name: immich_server + image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-v2} + volumes: + - /srv/media/immich:/data + - /etc/localtime:/etc/localtime:ro + env_file: + - ./immich.env + ports: + - "2283:2283" + depends_on: + - immich-redis + - immich-postgres + restart: unless-stopped + healthcheck: + disable: false + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + immich-machine-learning: + container_name: immich_machine_learning + image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-v2} + volumes: + - /srv/homelab/data/media/immich/model-cache:/cache + env_file: + - ./immich.env + restart: unless-stopped + healthcheck: + disable: false + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + immich-redis: + container_name: immich_redis + image: docker.io/valkey/valkey:9@sha256:546304417feac0874c3dd576e0952c6bb8f06bb4093ea0c9ca303c73cf458f63 + healthcheck: + test: redis-cli ping || exit 1 + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + immich-postgres: + container_name: immich_postgres + image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23 + env_file: + - ./immich.env + shm_size: 128mb + volumes: + - /srv/homelab/data/media/immich/postgres:/var/lib/postgresql/data + restart: unless-stopped + healthcheck: + disable: false + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/media/jellyfin.yaml b/stacks/media/jellyfin.yaml new file mode 100644 index 0000000..dec7f58 --- /dev/null +++ b/stacks/media/jellyfin.yaml @@ -0,0 +1,21 @@ +services: + jellyfin: + image: lscr.io/linuxserver/jellyfin:latest + container_name: jellyfin + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Moscow + ports: + - "8096:8096" + volumes: + - /srv/homelab/config/media/jellyfin:/config + - /srv/homelab/data/media/jellyfin/cache:/cache + - /srv/media:/srv/media + - /srv/media:/media + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/media/navidrome.yaml b/stacks/media/navidrome.yaml new file mode 100644 index 0000000..5569c9d --- /dev/null +++ b/stacks/media/navidrome.yaml @@ -0,0 +1,16 @@ +services: + navidrome: + image: deluan/navidrome:latest + container_name: navidrome + user: "1000:1000" + ports: + - "4533:4533" + volumes: + - /srv/homelab/data/media/navidrome:/data + - /srv/media/music:/music:ro + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/media/qbittorrent.yaml b/stacks/media/qbittorrent.yaml new file mode 100644 index 0000000..13eb071 --- /dev/null +++ b/stacks/media/qbittorrent.yaml @@ -0,0 +1,24 @@ +services: + qbittorrent: + image: lscr.io/linuxserver/qbittorrent:latest + container_name: qbittorrent + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Moscow + - WEBUI_PORT=8081 + ports: + - "8081:8081" + - "6881:6881" + - "6881:6881/udp" + volumes: + - /srv/homelab/config/media/qbittorrent:/config + - /srv/media/downloads:/downloads + - /srv/media/movies:/movies + - /srv/media/shows:/shows + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/media/seer.yaml b/stacks/media/seer.yaml new file mode 100644 index 0000000..747695f --- /dev/null +++ b/stacks/media/seer.yaml @@ -0,0 +1,18 @@ +services: + seer: + image: ghcr.io/seerr-team/seerr:latest + container_name: seer + environment: + - LOG_LEVEL=info + - TZ=Europe/Moscow + - PORT=5055 + ports: + - "5055:5055" + volumes: + - /srv/homelab/config/media/seer:/app/config + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/monitoring/compose.yaml b/stacks/monitoring/compose.yaml new file mode 100644 index 0000000..c949791 --- /dev/null +++ b/stacks/monitoring/compose.yaml @@ -0,0 +1,6 @@ +name: monitoring + +include: + - ./grafana.yaml + - ./glance.yaml + - ./portainer.yaml diff --git a/stacks/monitoring/glance.env b/stacks/monitoring/glance.env new file mode 100644 index 0000000..0cc786f --- /dev/null +++ b/stacks/monitoring/glance.env @@ -0,0 +1,22 @@ +# Variables for config (re-run docker compose up after changes). + +# Auth (optional) +MY_SECRET_TOKEN=123456 + +# *arr stack – API base URL and key (http from container) +RADARR_URL=http://radarr.home +RADARR_API_URL=http://radarr.home +RADARR_KEY=512cab3acfe84420957a9c8585560f8f + +SONARR_URL=http://sonarr.home +SONARR_API_URL=http://sonarr.home +SONARR_KEY=b3a84ef407ca42d99f5bc22fb8afc401 + +LIDARR_URL=http://lidarr.home +LIDARR_API_URL=http://lidarr.home +LIDARR_KEY=e0862cb9aa8c4c0f8115ec794c5bebc1 + +# Portainer (Settings → API → Enable API; endpoint ID from URL e.g. !#/2/docker → 2) +PORTAINER_URL=http://portainer.home +PORTAINER_ENDPOINT_ID=3 +PORTAINER_API_KEY=ptr_zmLlSaAbw8nQRoJlDRb006IDVtJLojneToSVNWJxo3c= diff --git a/stacks/monitoring/glance.env.example b/stacks/monitoring/glance.env.example new file mode 100644 index 0000000..8280251 --- /dev/null +++ b/stacks/monitoring/glance.env.example @@ -0,0 +1,13 @@ +MY_SECRET_TOKEN=change_me +RADARR_URL=http://radarr.home +RADARR_API_URL=http://radarr.home +RADARR_KEY=change_me +SONARR_URL=http://sonarr.home +SONARR_API_URL=http://sonarr.home +SONARR_KEY=change_me +LIDARR_URL=http://lidarr.home +LIDARR_API_URL=http://lidarr.home +LIDARR_KEY=change_me +PORTAINER_URL=http://portainer.home +PORTAINER_ENDPOINT_ID=1 +PORTAINER_API_KEY=change_me diff --git a/stacks/monitoring/glance.yaml b/stacks/monitoring/glance.yaml new file mode 100644 index 0000000..00ea1c3 --- /dev/null +++ b/stacks/monitoring/glance.yaml @@ -0,0 +1,24 @@ +services: + glance: + container_name: glance + image: glanceapp/glance + restart: unless-stopped + volumes: + - /srv/homelab/config/monitoring/glance:/app/config + - /srv/homelab/data/monitoring/glance/assets:/app/assets + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/custom-ca.crt:ro + - /srv/media:/srv/media:ro + - /backup:/backup:ro + ports: + - "9090:8080" + env_file: + - ./glance.env + environment: + - SSL_CERT_FILE=/etc/ssl/certs/custom-ca.crt + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/monitoring/grafana.yaml b/stacks/monitoring/grafana.yaml new file mode 100644 index 0000000..a29c89e --- /dev/null +++ b/stacks/monitoring/grafana.yaml @@ -0,0 +1,98 @@ +# One-time permissions (run on host): +# sudo chown -R 65534:65534 /srv/homelab/data/monitoring/prometheus +# sudo chown -R 10001:10001 /srv/homelab/data/monitoring/loki +# sudo chown -R 472:472 /srv/homelab/data/monitoring/grafana +# +services: + cadvisor: + image: gcr.io/cadvisor/cadvisor:latest + container_name: cadvisor + ports: + - "8088:8080" + volumes: + - /:/rootfs:ro + - /var/run:/var/run:ro + - /sys:/sys:ro + - /var/lib/docker:/var/lib/docker:ro + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + prometheus: + image: prom/prometheus:latest + container_name: prometheus + extra_hosts: + - "metrics.home:192.168.1.70" + ports: + - "9094:9090" + volumes: + - /srv/homelab/config/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - /srv/homelab/data/monitoring/prometheus:/prometheus + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + grafana: + image: grafana/grafana:latest + container_name: grafana + user: "472:65534" + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + - GF_FEATURE_TOGGLES_KUBERNETESDASHBOARDS=false + depends_on: + - loki + - prometheus + ports: + - "3034:3000" + volumes: + - /srv/homelab/data/monitoring/grafana:/var/lib/grafana + - /srv/homelab/config/monitoring/grafana/provisioning:/etc/grafana/provisioning:ro + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + loki: + image: grafana/loki:2.9.0 + container_name: loki + ports: + - "3100:3100" + command: -config.file=/etc/loki/config.yaml + volumes: + - /srv/homelab/config/monitoring/loki-config.yaml:/etc/loki/config.yaml:ro + - /srv/homelab/data/monitoring/loki:/loki + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" + + promtail: + image: grafana/promtail:2.9.0 + container_name: promtail + command: -config.file=/etc/promtail/config.yaml + depends_on: + - loki + volumes: + - /srv/homelab/config/monitoring/promtail-config.yaml:/etc/promtail/config.yaml:ro + - /var/log:/var/log:ro + - /etc/machine-id:/etc/machine-id:ro + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - /run/log/journal:/run/log/journal:ro + - /var/log/journal:/var/log/journal:ro + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/monitoring/portainer.yaml b/stacks/monitoring/portainer.yaml new file mode 100644 index 0000000..bf91f78 --- /dev/null +++ b/stacks/monitoring/portainer.yaml @@ -0,0 +1,18 @@ +services: + portainer: + container_name: portainer + image: portainer/portainer-ce:lts + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /srv/homelab/data/portainer/data:/data + ports: + - "9443:9443" + - "8000:8000" + environment: + - TRUSTED_ORIGINS=portainer.home + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/storage/compose.yaml b/stacks/storage/compose.yaml new file mode 100644 index 0000000..5dff8e5 --- /dev/null +++ b/stacks/storage/compose.yaml @@ -0,0 +1,5 @@ +name: storage + +include: + - ./copyparty.yaml + - ./gitea.yaml diff --git a/stacks/storage/copyparty.yaml b/stacks/storage/copyparty.yaml new file mode 100644 index 0000000..ea8b197 --- /dev/null +++ b/stacks/storage/copyparty.yaml @@ -0,0 +1,18 @@ +services: + copyparty: + image: copyparty/ac:latest + container_name: copyparty + command: ["-c", "/cfg/copyparty.conf"] + ports: + - "3923:3923" + volumes: + - /srv/homelab/config/storage/copyparty:/cfg + - /srv/homelab/config/storage/copyparty/initcfg:/z/initcfg:ro + - /srv/media:/srv/media + - /srv/homelab/data/storage/copyparty:/w + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "10m" + max-file: "5" diff --git a/stacks/storage/gitea.yaml b/stacks/storage/gitea.yaml new file mode 100644 index 0000000..fb34c55 --- /dev/null +++ b/stacks/storage/gitea.yaml @@ -0,0 +1,21 @@ +networks: + gitea: + external: false + +services: + server: + image: docker.gitea.com/gitea:1.25.4 + container_name: gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + restart: always + networks: + - gitea + volumes: + - /srv/homelab/data/storage/gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "3000:3000" + - "222:22"