Commit initial : infrastructure Ansible pour homeserver
- Playbooks Ansible avec rôles (common, cockpit, docker, services) - 30+ stacks Docker Compose avec reverse proxy Traefik - Ansible Vault pour gestion secrets - Intégration CrowdSec pour détection intrusions - Versions images Docker fixées pour reproductibilité
This commit is contained in:
commit
fd01ea59ee
125 changed files with 4768 additions and 0 deletions
0
stacks/glance/assets/user.css
Normal file
0
stacks/glance/assets/user.css
Normal file
66
stacks/glance/compose.yml
Normal file
66
stacks/glance/compose.yml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
services:
|
||||
glance:
|
||||
container_name: glance
|
||||
image: glanceapp/glance:v0.7.5
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config:/app/config
|
||||
- ./assets:/app/assets
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /mnt/storage:/mnt/storage:ro
|
||||
env_file: .env
|
||||
networks:
|
||||
- traefik_network
|
||||
- internal_glance
|
||||
secrets:
|
||||
- plex-token
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}-prod.rule=Host(`tellserv.fr`)"
|
||||
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}-prod.entryPoints=websecure"
|
||||
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}-prod.tls=true"
|
||||
- "traefik.http.routers.${COMPOSE_PROJECT_NAME}-prod.tls.certResolver=cloudflare"
|
||||
- "traefik.http.services.${COMPOSE_PROJECT_NAME}.loadbalancer.server.port=8080"
|
||||
|
||||
glance-containers-builder:
|
||||
build: ./container-builder
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./config/includes:/output
|
||||
- ./container-builder:/app/config
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal_glance
|
||||
entrypoint: >
|
||||
sh -c "pip install docker pyyaml requests beautifulsoup4 && while true; do python3 /app/generate_containers_block.py && sleep 60; done"
|
||||
|
||||
rss:
|
||||
container_name: glance-rss
|
||||
image: nginx:alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./rss:/usr/share/nginx/html:ro
|
||||
networks:
|
||||
- internal_glance
|
||||
|
||||
rss-builder:
|
||||
build: ./rss-builder
|
||||
container_name: rss-builder
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./updates:/updates:ro
|
||||
- ./rss:/rss
|
||||
networks:
|
||||
- internal_glance
|
||||
entrypoint: >
|
||||
sh -c "while true; do python3 /app/generate_rss.py && sleep 60; done"
|
||||
|
||||
networks:
|
||||
traefik_network:
|
||||
external: true
|
||||
internal_glance:
|
||||
name: glance_internal
|
||||
|
||||
secrets:
|
||||
plex-token:
|
||||
file: ./config/secrets/plex-token.txt
|
||||
12
stacks/glance/config/glance.yml
Normal file
12
stacks/glance/config/glance.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
server:
|
||||
assets-path: /app/assets
|
||||
|
||||
theme:
|
||||
# Note: assets are cached by the browser, changes to the CSS file
|
||||
# will not be reflected until the browser cache is cleared (Ctrl+F5)
|
||||
custom-css-file: /assets/user.css
|
||||
|
||||
pages:
|
||||
# It's not necessary to create a new file for each page and include it, you can simply
|
||||
# put its contents here, though multiple pages are easier to manage when separated
|
||||
- $include: home.yml
|
||||
184
stacks/glance/config/home.yml
Normal file
184
stacks/glance/config/home.yml
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
- name: Accueil
|
||||
columns:
|
||||
- size: small
|
||||
widgets:
|
||||
- type: calendar
|
||||
|
||||
- type: rss
|
||||
title: Journal du serveur
|
||||
style: vertical-list
|
||||
limit: 10
|
||||
collapse-after: 5
|
||||
feeds:
|
||||
- url: http://rss/index.xml
|
||||
|
||||
- size: full
|
||||
widgets:
|
||||
- type: search
|
||||
title: Recherche Whoogle
|
||||
search-engine: https://whoogle.tellserv.fr/search?q={QUERY}
|
||||
placeholder: Recherche sur Internet…
|
||||
new-tab: false
|
||||
autofocus: false
|
||||
|
||||
- type: docker-containers
|
||||
title: Conteneurs en cours
|
||||
limit: 10
|
||||
hide-by-default: true
|
||||
$include: includes/containers.yml
|
||||
|
||||
- size: small
|
||||
widgets:
|
||||
- type: weather
|
||||
title: Météo à Domfront
|
||||
location: Domfront, France
|
||||
units: metric
|
||||
hour-format: 24h
|
||||
|
||||
- type: server-stats
|
||||
title: Ressources serveur
|
||||
show-network: true
|
||||
servers:
|
||||
- type: local
|
||||
hide-mountpoints-by-default: true
|
||||
mountpoints:
|
||||
"/mnt/storage":
|
||||
name: Stockage principal
|
||||
hide: false
|
||||
|
||||
- type: custom-api
|
||||
title: Minecraft
|
||||
url: https://api.mcstatus.io/v2/status/java/minecraft.tellserv.fr
|
||||
cache: 30s
|
||||
template: |
|
||||
<div style="display:flex; align-items:center; gap:12px;">
|
||||
<div style="width:40px; height:40px; flex-shrink:0; border-radius:4px; display:flex; justify-content:center; align-items:center; overflow:hidden;">
|
||||
{{ if .JSON.Bool "online" }}
|
||||
<img src="{{ .JSON.String "icon" | safeURL }}" width="64" height="64" style="object-fit:contain;">
|
||||
{{ else }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" style="width:32px; height:32px; opacity:0.5;">
|
||||
<path fill-rule="evenodd" d="M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0l-2.97 2.97ZM12 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div style="flex-grow:1; min-width:0;">
|
||||
<a class="size-h4 block text-truncate color-highlight">
|
||||
{{ .JSON.String "host" }}
|
||||
{{ if .JSON.Bool "online" }}
|
||||
<span
|
||||
style="width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-positive); display: inline-block; vertical-align: middle;"
|
||||
data-popover-type="text"
|
||||
data-popover-text="Online"
|
||||
></span>
|
||||
{{ else }}
|
||||
<span
|
||||
style="width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-negative); display: inline-block; vertical-align: middle;"
|
||||
data-popover-type="text"
|
||||
data-popover-text="Offline"
|
||||
></span>
|
||||
{{ end }}
|
||||
</a>
|
||||
|
||||
<ul class="list-horizontal-text">
|
||||
<li>
|
||||
{{ if .JSON.Bool "online" }}
|
||||
<span>{{ .JSON.String "version.name_clean" }}</span>
|
||||
{{ else }}
|
||||
<span>Offline</span>
|
||||
{{ end }}
|
||||
</li>
|
||||
{{ if .JSON.Bool "online" }}
|
||||
<li data-popover-type="html">
|
||||
<div data-popover-html>
|
||||
{{ range .JSON.Array "players.list" }}{{ .String "name_clean" }}<br>{{ end }}
|
||||
</div>
|
||||
<p style="display:inline-flex;align-items:center;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6" style="height:1em;vertical-align:middle;margin-right:0.5em;">
|
||||
<path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ .JSON.Int "players.online" | formatNumber }}/{{ .JSON.Int "players.max" | formatNumber }} joueurs
|
||||
</p>
|
||||
</li>
|
||||
{{ else }}
|
||||
<li>
|
||||
<p style="display:inline-flex;align-items:center;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6" style="height:1em;vertical-align:middle;margin-right:0.5em;opacity:0.5;">
|
||||
<path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
0 joueurs
|
||||
</p>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
- type: custom-api
|
||||
title: Bibliothèques Plex
|
||||
cache: 5m
|
||||
options:
|
||||
base-url: https://plex.tellserv.fr
|
||||
api-key: ${secret:plex-token}
|
||||
template: |
|
||||
{{ $baseURL := .Options.StringOr "base-url" "" }}
|
||||
{{ $apiKey := .Options.StringOr "api-key" "" }}
|
||||
|
||||
{{ define "errorMsg" }}
|
||||
<div class="widget-error-header">
|
||||
<div class="color-negative size-h3">Erreur</div>
|
||||
<svg class="widget-error-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="break-all">{{ . }}</p>
|
||||
{{ end }}
|
||||
|
||||
{{ if or (eq $baseURL "") (eq $apiKey "") }}
|
||||
{{ template "errorMsg" "Paramètres manquants : base-url ou api-key" }}
|
||||
{{ else }}
|
||||
{{ $sectionsURL := printf "%s/library/sections" $baseURL }}
|
||||
{{ $sectionsCall := newRequest $sectionsURL
|
||||
| withHeader "Accept" "application/json"
|
||||
| withHeader "X-Plex-Token" $apiKey
|
||||
| getResponse }}
|
||||
|
||||
{{ if $sectionsCall.JSON.Exists "MediaContainer.Directory" }}
|
||||
{{ $sections := $sectionsCall.JSON.Array "MediaContainer.Directory" }}
|
||||
|
||||
<div class="cards" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px;">
|
||||
{{ range $section := $sections }}
|
||||
{{ $sectionID := $section.String "key" }}
|
||||
{{ $title := $section.String "title" }}
|
||||
{{ $type := $section.String "type" }}
|
||||
|
||||
{{ $color := "#666" }}
|
||||
{{ if eq $type "movie" }}
|
||||
{{ $color = "#e57373" }}
|
||||
{{ else if eq $type "show" }}
|
||||
{{ $color = "#7986cb" }}
|
||||
{{ else if eq $type "artist" }}
|
||||
{{ $color = "#81c784" }}
|
||||
{{ end }}
|
||||
|
||||
{{ $countURL := printf "%s/library/sections/%s/all" $baseURL $sectionID }}
|
||||
{{ $countCall := newRequest $countURL
|
||||
| withParameter "X-Plex-Token" $apiKey
|
||||
| withHeader "Accept" "application/json"
|
||||
| getResponse }}
|
||||
|
||||
{{ if $countCall.JSON.Exists "MediaContainer.size" }}
|
||||
{{ $count := $countCall.JSON.Int "MediaContainer.size" }}
|
||||
<div class="card" style="background-color: {{ $color }}; color: black; text-align: center; padding: 20px; border-radius: 12px;">
|
||||
<div style="font-size: 1.2rem; font-weight: bold;">{{ $title }}</div>
|
||||
<div style="font-size: 2.4rem; font-weight: bold; margin-top: 0.5em;">
|
||||
{{ $count }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ template "errorMsg" "Impossible de récupérer les bibliothèques Plex" }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
141
stacks/glance/config/includes/containers.yml
Normal file
141
stacks/glance/config/includes/containers.yml
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
containers:
|
||||
LibreChat:
|
||||
name: Librechat
|
||||
url: https://librechat.tellserv.fr
|
||||
icon: https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/librechat.png
|
||||
hide: false
|
||||
6e1a79ad77b9_plex:
|
||||
name: Plex
|
||||
url: https://plex.tellserv.fr
|
||||
icon: https://plex.tellserv.fr/favicon.ico
|
||||
hide: false
|
||||
stirlingpdf:
|
||||
name: Stirlingpdf
|
||||
url: https://stirlingpdf.tellserv.fr
|
||||
icon: https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png
|
||||
hide: false
|
||||
beszel:
|
||||
name: Beszel
|
||||
url: https://beszel.tellserv.fr
|
||||
icon: https://beszel.tellserv.fr/static/icon.svg
|
||||
hide: false
|
||||
audiobookshelf:
|
||||
name: Audiobookshelf
|
||||
url: https://audiobookshelf.tellserv.fr
|
||||
icon: https://audiobookshelf.tellserv.fr/audiobookshelf/favicon.ico
|
||||
hide: false
|
||||
whoogle-search:
|
||||
name: Whoogle
|
||||
url: https://whoogle.tellserv.fr
|
||||
icon: https://whoogle.tellserv.fr/static/img/favicon/apple-icon-57x57.png
|
||||
hide: false
|
||||
blog_ghost:
|
||||
name: Blog
|
||||
url: https://blog.tellserv.fr
|
||||
icon: https://blog.tellserv.fr/favicon.ico
|
||||
hide: false
|
||||
larabouillere_ghost:
|
||||
name: Larabouillere
|
||||
url: https://larabouillere.tellserv.fr
|
||||
icon: https://larabouillere.tellserv.fr/favicon.ico
|
||||
hide: false
|
||||
freshrss:
|
||||
name: Freshrss
|
||||
url: https://freshrss.tellserv.fr
|
||||
icon: https://freshrss.tellserv.fr/../favicon.ico
|
||||
hide: false
|
||||
webdav:
|
||||
name: Webdav
|
||||
url: https://webdav.tellserv.fr
|
||||
icon: mdi:web
|
||||
hide: false
|
||||
gotify:
|
||||
name: Gotify
|
||||
url: https://gotify.tellserv.fr
|
||||
icon: https://gotify.tellserv.fr/static/apple-touch-icon-57x57.png
|
||||
hide: false
|
||||
vikunja-vikunja-1:
|
||||
name: Vikunja
|
||||
url: https://vikunja.tellserv.fr
|
||||
icon: https://vikunja.tellserv.fr/favicon.ico
|
||||
hide: false
|
||||
searxng:
|
||||
name: Searxng
|
||||
url: https://searxng.tellserv.fr
|
||||
icon: https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/searxng.png
|
||||
hide: false
|
||||
paperless:
|
||||
name: Paperless
|
||||
url: https://paperless.tellserv.fr
|
||||
icon: https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/paperless-ngx.png
|
||||
hide: false
|
||||
pc-builds:
|
||||
name: Pc-builds
|
||||
url: https://pc-builds.tellserv.fr
|
||||
icon: https://pc-builds.tellserv.fr/favicon.ico
|
||||
hide: false
|
||||
clipcascade-clipcascade-1:
|
||||
name: Clipcascade
|
||||
url: https://clipcascade.tellserv.fr
|
||||
icon: https://clipcascade.tellserv.fr/assets/images/logo.png
|
||||
hide: false
|
||||
vaultwarden:
|
||||
name: Vaultwarden
|
||||
url: https://vaultwarden.tellserv.fr
|
||||
icon: https://vaultwarden.tellserv.fr/images/apple-touch-icon.png
|
||||
hide: false
|
||||
yamtrack:
|
||||
name: Yamtrack
|
||||
url: https://yamtrack.tellserv.fr
|
||||
icon: https://yamtrack.tellserv.fr/static/favicon/apple-touch-icon.png
|
||||
hide: false
|
||||
qbittorrent:
|
||||
name: Qbittorrent
|
||||
url: https://qbittorrent.tellserv.fr
|
||||
icon: https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/qbittorrent.png
|
||||
hide: false
|
||||
photoprism-photoprism-1:
|
||||
name: Photoprism
|
||||
url: https://photoprism.tellserv.fr
|
||||
icon: https://photoprism.tellserv.fr/static/icons/logo/512.png
|
||||
hide: false
|
||||
pingvin:
|
||||
name: Pingvin
|
||||
url: https://pingvin.tellserv.fr
|
||||
icon: https://pingvin.tellserv.fr/img/favicon.ico
|
||||
hide: false
|
||||
kavita:
|
||||
name: Kavita
|
||||
url: https://kavita.tellserv.fr
|
||||
icon: https://kavita.tellserv.fr/assets/icons/apple-touch-icon.png
|
||||
hide: false
|
||||
glance:
|
||||
name: Glance
|
||||
url: https://tellserv.fr
|
||||
icon: https://tellserv.fr/static/79abad6150/app-icon.png
|
||||
hide: false
|
||||
mobilizon-mobilizon-1:
|
||||
name: Mobilizon
|
||||
url: https://mobilizon.tellserv.fr
|
||||
icon: https://mobilizon.tellserv.fr/img/icons/apple-touch-icon-152x152.png
|
||||
hide: false
|
||||
joal:
|
||||
name: Joal
|
||||
url: https://joal.tellserv.fr
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/joal.png
|
||||
hide: false
|
||||
feedropolis-app:
|
||||
name: Feedropolis
|
||||
url: https://feedropolis.tellserv.fr
|
||||
icon: https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/rss-com.png
|
||||
hide: false
|
||||
etebase:
|
||||
name: Etesync
|
||||
url: https://etesync.tellserv.fr
|
||||
icon: https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/etesync.png
|
||||
hide: false
|
||||
bin:
|
||||
name: Bin
|
||||
url: https://bin.tellserv.fr
|
||||
icon: https://microbin.eu/img/logo-square.png
|
||||
hide: false
|
||||
1
stacks/glance/config/secrets/plex-token.txt
Normal file
1
stacks/glance/config/secrets/plex-token.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
L3V-pvV7o5ybND3T6Rr5
|
||||
9
stacks/glance/container-builder/Dockerfile
Normal file
9
stacks/glance/container-builder/Dockerfile
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY generate_containers_block.py .
|
||||
|
||||
RUN pip install docker pyyaml requests beautifulsoup4
|
||||
|
||||
CMD ["python3", "generate_containers_block.py"]
|
||||
124
stacks/glance/container-builder/generate_containers_block.py
Normal file
124
stacks/glance/container-builder/generate_containers_block.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env python3
|
||||
import docker
|
||||
import yaml
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
client = docker.from_env()
|
||||
containers = client.containers.list()
|
||||
output = {}
|
||||
|
||||
print(f"[•] Détection de {len(containers)} conteneur(s)...\n")
|
||||
|
||||
override_path = "/app/config/icon_overrides.json"
|
||||
overrides = {}
|
||||
|
||||
# Charger les overrides si présents
|
||||
if os.path.exists(override_path):
|
||||
with open(override_path, "r", encoding="utf-8") as f:
|
||||
overrides = json.load(f)
|
||||
|
||||
def extract_image_name(image):
|
||||
name = image.split("/")[-1]
|
||||
return name.split(":")[0] if ":" in name else name
|
||||
|
||||
def find_favicon(url, project_name, image_name):
|
||||
print(f"[→] Recherche favicon pour {url}")
|
||||
|
||||
# Override manuel ?
|
||||
if project_name in overrides and overrides[project_name]:
|
||||
print(f"[✓] Favicon forcé depuis override : {overrides[project_name]}")
|
||||
return overrides[project_name]
|
||||
|
||||
icon_url = None
|
||||
try:
|
||||
r = requests.get(url, timeout=5)
|
||||
r.raise_for_status()
|
||||
soup = BeautifulSoup(r.text, 'html.parser')
|
||||
icons = soup.find_all("link", rel=lambda x: x and 'icon' in x.lower())
|
||||
if icons:
|
||||
href = icons[0].get("href")
|
||||
if href:
|
||||
icon_url = href if href.startswith("http") else url.rstrip("/") + "/" + href.lstrip("/")
|
||||
print(f"[✓] Favicon trouvé via HTML : {icon_url}")
|
||||
return icon_url
|
||||
else:
|
||||
print("[✗] Aucun favicon trouvé dans HTML")
|
||||
except Exception:
|
||||
print(f"[✗] Impossible de charger HTML depuis {url}")
|
||||
|
||||
test_favicon = f"{url.rstrip('/')}/favicon.ico"
|
||||
try:
|
||||
r = requests.get(test_favicon, timeout=5)
|
||||
if r.ok:
|
||||
print(f"[✓] Favicon trouvé via /favicon.ico : {test_favicon}")
|
||||
return test_favicon
|
||||
except:
|
||||
pass
|
||||
|
||||
github_icon = f"https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/{project_name}.png"
|
||||
try:
|
||||
r = requests.get(github_icon, timeout=5)
|
||||
if r.ok:
|
||||
print(f"[✓] Favicon fallback via GitHub (project_name) : {github_icon}")
|
||||
return github_icon
|
||||
except:
|
||||
print(f"[✗] Pas d’icône GitHub pour {project_name}")
|
||||
|
||||
github_icon = f"https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/{image_name}.png"
|
||||
try:
|
||||
r = requests.get(github_icon, timeout=5)
|
||||
if r.ok:
|
||||
print(f"[✓] Favicon fallback via GitHub (image) : {github_icon}")
|
||||
return github_icon
|
||||
except:
|
||||
print(f"[✗] Pas d’icône GitHub pour {image_name}")
|
||||
|
||||
print("[✗] Aucun favicon disponible\n")
|
||||
return None
|
||||
|
||||
for container in containers:
|
||||
labels = container.labels
|
||||
name = container.name
|
||||
image_name = extract_image_name(container.image.tags[0]) if container.image.tags else container.image.short_id
|
||||
project_name = labels.get("com.docker.compose.project", name).lower()
|
||||
|
||||
print(f"[•] Analyse du conteneur : {name} (projet : {project_name})")
|
||||
|
||||
domain = None
|
||||
for key, value in labels.items():
|
||||
if key.startswith(f"traefik.http.routers.{project_name}-prod.rule") and "Host(`" in value:
|
||||
domain = value.split("Host(`")[1].split("`)")[0]
|
||||
break
|
||||
|
||||
if domain:
|
||||
url = f"https://{domain}"
|
||||
icon = find_favicon(url, project_name, image_name)
|
||||
|
||||
output[name] = {
|
||||
"name": project_name.capitalize(),
|
||||
"url": url,
|
||||
"icon": icon or "mdi:web",
|
||||
"hide": False
|
||||
}
|
||||
|
||||
if project_name not in overrides:
|
||||
overrides[project_name] = ""
|
||||
|
||||
print(f"[✓] Conteneur ajouté : {project_name} → {url}\n")
|
||||
else:
|
||||
print("[!] Aucune règle Traefik -prod trouvée pour ce conteneur.\n")
|
||||
|
||||
# Générer fichiers
|
||||
os.makedirs("/output", exist_ok=True)
|
||||
|
||||
with open("/output/containers.yml", "w", encoding="utf-8") as f:
|
||||
yaml.dump({"containers": output}, f, sort_keys=False)
|
||||
|
||||
with open(override_path, "w", encoding="utf-8") as f:
|
||||
json.dump(overrides, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print("✅ Fichier containers.yml généré : /output/containers.yml")
|
||||
print(f"✅ Fichier overrides mis à jour : {override_path}")
|
||||
32
stacks/glance/container-builder/icon_overrides.json
Normal file
32
stacks/glance/container-builder/icon_overrides.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"glance": "",
|
||||
"paperless": "",
|
||||
"librechat": "https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/librechat.png",
|
||||
"blog": "",
|
||||
"larabouillere": "",
|
||||
"yamtrack": "",
|
||||
"photoprism": "",
|
||||
"plex": "",
|
||||
"stirlingpdf": "https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling.png",
|
||||
"etesync": "",
|
||||
"crafty": "",
|
||||
"bin": "https://microbin.eu/img/logo-square.png",
|
||||
"beszel": "",
|
||||
"audiobookshelf": "",
|
||||
"gotify": "",
|
||||
"clipcascade": "",
|
||||
"freshrss": "",
|
||||
"feedropolis": "https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/rss-com.png",
|
||||
"joal": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/joal.png",
|
||||
"qbittorrent": "https://raw.githubusercontent.com/selfhst/icons/refs/heads/main/png/qbittorrent.png",
|
||||
"kavita": "",
|
||||
"whoogle": "",
|
||||
"pingvin": "",
|
||||
"vikunja": "",
|
||||
"vaultwarden": "",
|
||||
"mobilizon": "",
|
||||
"random-draft": "",
|
||||
"pc-builds": "",
|
||||
"searxng": "",
|
||||
"webdav": ""
|
||||
}
|
||||
7
stacks/glance/rss-builder/Dockerfile
Normal file
7
stacks/glance/rss-builder/Dockerfile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
FROM python:3.11-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY generate_rss.py .
|
||||
|
||||
CMD ["python3", "generate_rss.py"]
|
||||
37
stacks/glance/rss-builder/generate_rss.py
Normal file
37
stacks/glance/rss-builder/generate_rss.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python3
|
||||
import re
|
||||
from datetime import datetime
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
rss_path = "/rss/index.xml"
|
||||
md_path = "/updates/updates.md"
|
||||
|
||||
with open(md_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
entries = re.findall(r"## (\d{4}-\d{2}-\d{2}) - (.+?)\n(.+?)(?=\n## |\Z)", content, re.DOTALL)
|
||||
|
||||
rss_items = ""
|
||||
for date_str, title, description in entries:
|
||||
pubdate = datetime.strptime(date_str, "%Y-%m-%d").strftime("%a, %d %b %Y 12:00:00 GMT")
|
||||
rss_items += f""" <item>
|
||||
<title>{escape(title.strip())}</title>
|
||||
<link>http://rss/index.xml#{escape(title.strip().replace(" ", "-").lower())}</link>
|
||||
<pubDate>{pubdate}</pubDate>
|
||||
<description>{escape(description.strip())}</description>
|
||||
</item>\n"""
|
||||
|
||||
rss = f"""<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Updates serveur</title>
|
||||
<link>http://rss/index.xml</link>
|
||||
<description>Changelog des services Tellserv</description>
|
||||
{rss_items}</channel>
|
||||
</rss>
|
||||
"""
|
||||
|
||||
with open(rss_path, "w", encoding="utf-8") as f:
|
||||
f.write(rss)
|
||||
|
||||
print("Flux RSS généré.")
|
||||
44
stacks/glance/rss/index.xml
Normal file
44
stacks/glance/rss/index.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Updates serveur</title>
|
||||
<link>http://rss/index.xml</link>
|
||||
<description>Changelog des services Tellserv</description>
|
||||
<item>
|
||||
<title>Changement d'infra : 2 instances Traefik, une par interface réseau (Interne / Externe).</title>
|
||||
<link>http://rss/index.xml#changement-d'infra-:-2-instances-traefik,-une-par-interface-réseau-(interne-/-externe).</link>
|
||||
<pubDate>Sun, 01 Jun 2025 12:00:00 GMT</pubDate>
|
||||
<description>Une instance Traefik permet d'exposer sur *.tellserv.fr et l'autre sert pour les services externes afin de permettre un meilleur cloisonnement.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Glance remplace Flame : Nouveau dashboard, détection auto des services, widget Plex.</title>
|
||||
<link>http://rss/index.xml#glance-remplace-flame-:-nouveau-dashboard,-détection-auto-des-services,-widget-plex.</link>
|
||||
<pubDate>Sat, 24 May 2025 12:00:00 GMT</pubDate>
|
||||
<description>Ajout d’un script pour détecter automatiquement les services exposés, et intégration d’un widget Plex affichant les différentes bibliothèques et leur nombre de médias.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Nouvelle collection Plex : Films non vus choisis au hasard.</title>
|
||||
<link>http://rss/index.xml#nouvelle-collection-plex-:-films-non-vus-choisis-au-hasard.</link>
|
||||
<pubDate>Fri, 23 May 2025 12:00:00 GMT</pubDate>
|
||||
<description>Une collection aléatoire a été ajoutée sur Plex, permettant de voir un film non vu encore.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Relance des services restants : Minecraft, Photoprism...</title>
|
||||
<link>http://rss/index.xml#relance-des-services-restants-:-minecraft,-photoprism...</link>
|
||||
<pubDate>Fri, 09 May 2025 12:00:00 GMT</pubDate>
|
||||
<description>Les derniers services encore non fonctionnels, comme le serveur Minecraft, sont relancés.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Notifications via Gotify : Mise en place d’un système d’alertes centralisé.</title>
|
||||
<link>http://rss/index.xml#notifications-via-gotify-:-mise-en-place-d’un-système-d’alertes-centralisé.</link>
|
||||
<pubDate>Wed, 07 May 2025 12:00:00 GMT</pubDate>
|
||||
<description>Mise en place du système de notifications centralisé avec Gotify.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Migration vers Proxmox : Serveur déplacé dans une VM Proxmox.</title>
|
||||
<link>http://rss/index.xml#migration-vers-proxmox-:-serveur-déplacé-dans-une-vm-proxmox.</link>
|
||||
<pubDate>Tue, 06 May 2025 12:00:00 GMT</pubDate>
|
||||
<description>Migration complète du serveur principal dans une machine virtuelle sous Proxmox.</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
20
stacks/glance/updates/updates.md
Normal file
20
stacks/glance/updates/updates.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Mises à jour du serveur
|
||||
|
||||
## 2025-06-01 - Changement d'infra : 2 instances Traefik, une par interface réseau (Interne / Externe).
|
||||
Une instance Traefik permet d'exposer sur *.tellserv.fr et l'autre sert pour les services externes afin de permettre un meilleur cloisonnement.
|
||||
|
||||
## 2025-05-24 - Glance remplace Flame : Nouveau dashboard, détection auto des services, widget Plex.
|
||||
Ajout d’un script pour détecter automatiquement les services exposés, et intégration d’un widget Plex affichant les différentes bibliothèques et leur nombre de médias.
|
||||
|
||||
## 2025-05-23 - Nouvelle collection Plex : Films non vus choisis au hasard.
|
||||
Une collection aléatoire a été ajoutée sur Plex, permettant de voir un film non vu encore.
|
||||
|
||||
## 2025-05-09 - Relance des services restants : Minecraft, Photoprism...
|
||||
Les derniers services encore non fonctionnels, comme le serveur Minecraft, sont relancés.
|
||||
|
||||
## 2025-05-07 - Notifications via Gotify : Mise en place d’un système d’alertes centralisé.
|
||||
Mise en place du système de notifications centralisé avec Gotify.
|
||||
|
||||
## 2025-05-06 - Migration vers Proxmox : Serveur déplacé dans une VM Proxmox.
|
||||
Migration complète du serveur principal dans une machine virtuelle sous Proxmox.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue