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:
Tellsanguis 2025-11-23 19:40:17 +01:00
commit fd01ea59ee
125 changed files with 4768 additions and 0 deletions

View file

66
stacks/glance/compose.yml Normal file
View 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

View 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

View 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 }}

View 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

View file

@ -0,0 +1 @@
L3V-pvV7o5ybND3T6Rr5

View 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"]

View 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 dicô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 dicô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}")

View 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": ""
}

View file

@ -0,0 +1,7 @@
FROM python:3.11-alpine
WORKDIR /app
COPY generate_rss.py .
CMD ["python3", "generate_rss.py"]

View 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é.")

View 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 dun script pour détecter automatiquement les services exposés, et intégration dun 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 dun système dalertes centralisé.</title>
<link>http://rss/index.xml#notifications-via-gotify-:-mise-en-place-dun-système-dalertes-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>

View 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 dun script pour détecter automatiquement les services exposés, et intégration dun 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 dun système dalertes 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.