| ... |
... |
@@ -1,18 +1,13 @@ |
| 1 |
|
-Tableau de bord d'accueil : liens vers tous les services auto-hébergés, avec leur état en direct et des widgets, répartis sur une page d'accueil et trois pages secondaires. |
|
1 |
+Tableau de bord d'accueil du serveur principal : liens vers les services auto-hébergés avec leur état, et widgets. Il compte quatre pages : l'accueil (##conf.yml##) et trois pages secondaires. |
| 2 |
2 |
|
| 3 |
|
-== Fonctionnalités utilisées == |
|
3 |
+{{toc/}} |
| 4 |
4 |
|
| 5 |
|
-* Sections et liens vers les services, avec icônes stockées en local ou issues des jeux d'icônes intégrés. |
| 6 |
|
-* Indicateur d'état de chaque service (vérification de disponibilité). |
| 7 |
|
-* Configuration multi-pages : une page d'accueil (##conf.yml##) et trois pages secondaires déclarées dans sa section ##pages:##. |
| 8 |
|
-* Widgets de contenu dynamique, dont certains interrogent des services locaux ou des API externes avec des identifiants (AdGuard Home, Nextcloud, API de vols et d'actualités). |
| 9 |
|
-* Cartes de statistiques GitHub fournies par une instance locale de github-readme-stats. |
| 10 |
|
-* Accès protégé par Authelia au niveau de SWAG : aucune page ni aucun fichier de configuration n'est servi sans connexion. |
| 11 |
|
-* Proxy CORS personnalisé dans SWAG, qui remplace celui de Dashy pour les widgets en ##useProxy: true##. |
| 12 |
|
-* Configuration modifiable depuis l'interface, enregistrée dans les fichiers YAML. |
|
5 |
+Les valeurs propres à l'infrastructure sont remplacées par des marqueurs ##<ENTRE_CHEVRONS>## : ##<DOMAINE>##, adresses IP, comptes, mots de passe et clés. |
| 13 |
13 |
|
| 14 |
|
-== Stack == |
|
7 |
+== Déploiement == |
| 15 |
15 |
|
|
9 |
+=== Stack Portainer === |
|
10 |
+ |
| 16 |
16 |
{{code language="yaml"}} |
| 17 |
17 |
services: |
| 18 |
18 |
dashy: |
| ... |
... |
@@ -42,22 +42,19 @@ |
| 42 |
42 |
external: true |
| 43 |
43 |
{{/code}} |
| 44 |
44 |
|
| 45 |
|
-* ##NODE_TLS_REJECT_UNAUTHORIZED=0## : le serveur de Dashy accepte les certificats non reconnus lors des vérifications d'état et dans son proxy CORS, nécessaire pour les services locaux en https avec un certificat auto-signé. |
| 46 |
|
-* ##UID## / ##GID## : propriétaire des fichiers écrits dans ##user-data## (sauvegarde depuis l'interface). |
| 47 |
|
-* Port ##4000## : accès direct depuis le LAN, sans SWAG ni Authelia. L'accès normal passe par SWAG, qui joint le conteneur par son nom sur le réseau ##proxy##, port interne ##8080##. Pour que tout accès passe par Authelia, supprimer cette publication de port : SWAG n'en a pas besoin. |
| 48 |
|
-* Volume : tout le dossier ##user-data## est monté dans ##/app/user-data##. Dashy sert ce dossier à la racine du site : ##conf.yml##, les pages secondaires et ##item-icons/## sont accessibles par leur nom. |
| 49 |
|
-* Réseau ##proxy## : réseau Docker externe partagé avec SWAG, à créer avant le premier déploiement (##docker network create proxy##). |
| 50 |
|
-* Mises à jour de l'image : [[Watchtower>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Watchtower.WebHome]]. |
|
40 |
+* ##NODE_TLS_REJECT_UNAUTHORIZED=0## : Dashy accepte les certificats auto-signés des services locaux (vérifications d'état, proxy CORS). |
|
41 |
+* ##user-data## contient toute la configuration. Dashy sert ce dossier à la racine du site. |
|
42 |
+* Le port ##4000## donne un accès direct depuis le LAN, sans SWAG ni Authelia. SWAG n'en a pas besoin : il joint le conteneur par le réseau ##proxy##. |
| 51 |
51 |
|
| 52 |
|
-=== Exposition derrière SWAG === |
|
44 |
+=== SWAG === |
| 53 |
53 |
|
| 54 |
|
-Fichier ##<APPDATA>/swag/config/nginx/proxy-confs/30-dashy.subdomain.conf##. Chaque ##location## inclut ##authelia-location.conf##, selon le même schéma que les autres services protégés. Voir aussi [[SWAG>>Wiki Batcave John.Infrastructure.ServeurPrincipal.SWAG.WebHome]]. |
|
46 |
+##<APPDATA>/swag/config/nginx/proxy-confs/30-dashy.subdomain.conf## : |
| 55 |
55 |
|
| 56 |
56 |
{{code language="nginx"}} |
| 57 |
57 |
server { |
| 58 |
58 |
listen 443 ssl; |
| 59 |
59 |
listen [::]:443 ssl; |
| 60 |
|
- server_name <DOMAINE_DASHY>; |
|
52 |
+ server_name dash.<DOMAINE>; |
| 61 |
61 |
|
| 62 |
62 |
include /config/nginx/ssl.conf; |
| 63 |
63 |
include /config/nginx/snippets/robots-default.conf; |
| ... |
... |
@@ -64,14 +64,15 @@ |
| 64 |
64 |
|
| 65 |
65 |
client_max_body_size 25m; |
| 66 |
66 |
|
| 67 |
|
- # Authelia : tout le domaine exige une connexion |
|
59 |
+ # Authelia devant tout le domaine |
| 68 |
68 |
include /config/nginx/authelia-server.conf; |
| 69 |
69 |
|
|
62 |
+ # Upstream Dashy |
| 70 |
70 |
set $upstream_app dashy; |
| 71 |
71 |
set $upstream_port 8080; |
| 72 |
72 |
set $upstream_proto http; |
| 73 |
73 |
|
| 74 |
|
- # Interface web du Pico W, relayée sous /pico/ |
|
67 |
+ # (facultatif) proxy vers /pico/ |
| 75 |
75 |
location /pico/ { |
| 76 |
76 |
include /config/nginx/authelia-location.conf; |
| 77 |
77 |
proxy_pass http://<IP_PICO>/; |
| ... |
... |
@@ -79,7 +79,7 @@ |
| 79 |
79 |
proxy_set_header Connection close; |
| 80 |
80 |
} |
| 81 |
81 |
|
| 82 |
|
- # API de Dashy (sauvegarde de la config, vérifications d'état...) |
|
75 |
+ # API de Dashy (sauvegarde de la config, vérifications d'état) |
| 83 |
83 |
location ^~ /api/ { |
| 84 |
84 |
include /config/nginx/authelia-location.conf; |
| 85 |
85 |
include /config/nginx/proxy.conf; |
| ... |
... |
@@ -86,83 +86,90 @@ |
| 86 |
86 |
include /config/nginx/resolver.conf; |
| 87 |
87 |
proxy_pass $upstream_proto://$upstream_app:$upstream_port; |
| 88 |
88 |
} |
| 89 |
|
- |
| 90 |
|
- # Proxy CORS des widgets, traité par SWAG et non par Dashy |
| 91 |
91 |
location = /cors-proxy { |
| 92 |
92 |
include /config/nginx/authelia-location.conf; |
| 93 |
|
- set $target $http_target_url; |
| 94 |
|
- if ($target = "") { return 400; } |
|
84 |
+ # Dashy envoie la cible dans ce header |
|
85 |
+ set $target $http_target_url; |
|
86 |
+ if ($target = "") { return 400; } |
| 95 |
95 |
|
| 96 |
|
- add_header Access-Control-Allow-Origin "*" always; |
| 97 |
|
- add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; |
| 98 |
|
- add_header Access-Control-Allow-Headers "*" always; |
| 99 |
|
- if ($request_method = OPTIONS) { return 204; } |
|
88 |
+ add_header Access-Control-Allow-Origin "*" always; |
|
89 |
+ add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; |
|
90 |
+ add_header Access-Control-Allow-Headers "*" always; |
|
91 |
+ if ($request_method = OPTIONS) { return 204; } |
| 100 |
100 |
|
| 101 |
|
- proxy_ssl_server_name on; |
| 102 |
|
- proxy_ssl_verify off; |
|
93 |
+ # TLS (certs auto-signés possibles sur les cibles LAN, ex: Nextcloud) |
|
94 |
+ proxy_ssl_server_name on; |
|
95 |
+ proxy_ssl_verify off; |
| 103 |
103 |
|
| 104 |
|
- set $auth_header ""; |
| 105 |
|
- set $accept_header ""; |
| 106 |
|
- set $ocs_header ""; |
| 107 |
|
- set $host_override ""; |
|
97 |
+ # Par défaut, on ne fait pas confiance à l'input |
|
98 |
+ set $auth_header ""; |
|
99 |
+ set $accept_header ""; |
|
100 |
+ set $ocs_header ""; |
|
101 |
+ set $host_override ""; |
| 108 |
108 |
|
| 109 |
|
- access_by_lua_block { |
| 110 |
|
- local target = ngx.var.target |
| 111 |
|
- local host, port = target:match("^https?://([^/:]+):?(%d*)") |
| 112 |
|
- if not host then |
| 113 |
|
- ngx.status = 400 |
| 114 |
|
- ngx.header["Content-Type"] = "application/json" |
| 115 |
|
- ngx.say('{"error":"Target-URL invalide"}') |
| 116 |
|
- return ngx.exit(400) |
| 117 |
|
- end |
|
103 |
+ access_by_lua_block { |
|
104 |
+ local target = ngx.var.target |
| 118 |
118 |
|
| 119 |
|
- -- Cibles LAN autorisées (widgets Dashy internes) |
| 120 |
|
- local allowed_internal = { ["<IP_SERVEUR>"] = true, ["<IP_ADGUARD>"] = true, ["<IP_PICO>"] = true } |
| 121 |
|
- local function is_private_ip(h) |
| 122 |
|
- if h == "localhost" then return true end |
| 123 |
|
- local a, b = h:match("^(%d+)%.(%d+)%.%d+%.%d+$") |
| 124 |
|
- if not a then return false end |
| 125 |
|
- a, b = tonumber(a), tonumber(b) |
| 126 |
|
- if a == 127 or a == 10 or a == 0 then return true end |
| 127 |
|
- if a == 172 and b >= 16 and b <= 31 then return true end |
| 128 |
|
- if a == 192 and b == 168 then return true end |
| 129 |
|
- if a == 169 and b == 254 then return true end |
| 130 |
|
- return false |
| 131 |
|
- end |
|
106 |
+ local host, port = target:match("^https?://([^/:]+):?(%d*)") |
|
107 |
+ if not host then |
|
108 |
+ ngx.status = 400 |
|
109 |
+ ngx.header["Content-Type"] = "application/json" |
|
110 |
+ ngx.say('{"error":"Target-URL invalide"}') |
|
111 |
+ return ngx.exit(400) |
|
112 |
+ end |
| 132 |
132 |
|
| 133 |
|
- -- Anti-SSRF : LAN, loopback et metadata bloqués sauf liste blanche |
| 134 |
|
- if is_private_ip(host) and not allowed_internal[host] then |
| 135 |
|
- ngx.status = 403 |
| 136 |
|
- ngx.header["Content-Type"] = "application/json" |
| 137 |
|
- ngx.say('{"error":"Cible interne non autorisée"}') |
| 138 |
|
- return ngx.exit(403) |
| 139 |
|
- end |
|
114 |
+ -- Cibles LAN explicitement autorisées (widgets Dashy internes) |
|
115 |
+ local allowed_internal = { ["<IP_SERVEUR>"] = true, ["<IP_ADGUARD>"] = true, ["<IP_PICO>"] = true } |
|
116 |
+ local function is_private_ip(h) |
|
117 |
+ if h == "localhost" then return true end |
|
118 |
+ local a, b = h:match("^(%d+)%.(%d+)%.%d+%.%d+$") |
|
119 |
+ if not a then return false end -- pas une IPv4 littérale -> traité comme hôte public |
|
120 |
+ a, b = tonumber(a), tonumber(b) |
|
121 |
+ if a == 127 or a == 10 or a == 0 then return true end |
|
122 |
+ if a == 172 and b >= 16 and b <= 31 then return true end -- 172.16.0.0/12 (docker inclus) |
|
123 |
+ if a == 192 and b == 168 then return true end |
|
124 |
+ if a == 169 and b == 254 then return true end -- link-local / metadata cloud |
|
125 |
+ return false |
|
126 |
+ end |
| 140 |
140 |
|
| 141 |
|
- ngx.var.host_override = (port ~= "" and port ~= nil) and (host .. ":" .. port) or host |
|
128 |
+ -- Anti-SSRF : on bloque tout le LAN/loopback/metadata, sauf les cibles explicitement whitelistées |
|
129 |
+ if is_private_ip(host) and not allowed_internal[host] then |
|
130 |
+ ngx.status = 403 |
|
131 |
+ ngx.header["Content-Type"] = "application/json" |
|
132 |
+ ngx.say('{"error":"Cible interne non autorisée"}') |
|
133 |
+ return ngx.exit(403) |
|
134 |
+ end |
| 142 |
142 |
|
| 143 |
|
- -- En-têtes envoyés par Dashy en JSON dans CustomHeaders |
| 144 |
|
- local cjson = require "cjson.safe" |
| 145 |
|
- local ch = ngx.req.get_headers()["customheaders"] |
| 146 |
|
- if ch then |
| 147 |
|
- local obj = cjson.decode(ch) |
| 148 |
|
- if obj then |
| 149 |
|
- if obj["Authorization"] then ngx.var.auth_header = obj["Authorization"] end |
| 150 |
|
- if obj["Accept"] then ngx.var.accept_header = obj["Accept"] end |
| 151 |
|
- if obj["OCS-APIREQUEST"] ~= nil then ngx.var.ocs_header = tostring(obj["OCS-APIREQUEST"]) end |
| 152 |
|
- end |
|
136 |
+ ngx.var.host_override = (port ~= "" and port ~= nil) and (host .. ":" .. port) or host |
|
137 |
+ |
|
138 |
+ -- Parse le JSON envoyé dans customheaders |
|
139 |
+ local cjson = require "cjson.safe" |
|
140 |
+ local hdrs = ngx.req.get_headers() |
|
141 |
+ local ch = hdrs["customheaders"] |
|
142 |
+ if ch then |
|
143 |
+ local obj = cjson.decode(ch) |
|
144 |
+ if obj then |
|
145 |
+ if obj["Authorization"] then ngx.var.auth_header = obj["Authorization"] end |
|
146 |
+ if obj["Accept"] then ngx.var.accept_header = obj["Accept"] end |
|
147 |
+ if obj["OCS-APIREQUEST"] ~= nil then |
|
148 |
+ ngx.var.ocs_header = tostring(obj["OCS-APIREQUEST"]) |
| 153 |
153 |
end |
| 154 |
|
- } |
|
150 |
+ end |
|
151 |
+ end |
|
152 |
+ } |
| 155 |
155 |
|
| 156 |
|
- proxy_set_header Authorization $auth_header; |
| 157 |
|
- proxy_set_header Accept $accept_header; |
| 158 |
|
- proxy_set_header OCS-APIREQUEST $ocs_header; |
| 159 |
|
- proxy_set_header Host $host_override; |
| 160 |
|
- proxy_set_header X-Forwarded-Proto https; |
|
154 |
+ # Injecte les vrais headers vers la cible |
|
155 |
+ proxy_set_header Authorization $auth_header; |
|
156 |
+ proxy_set_header Accept $accept_header; |
|
157 |
+ proxy_set_header OCS-APIREQUEST $ocs_header; |
| 161 |
161 |
|
| 162 |
|
- proxy_pass $target; |
| 163 |
|
- } |
|
159 |
+ proxy_set_header Host $host_override; |
|
160 |
+ proxy_set_header X-Forwarded-Proto https; |
| 164 |
164 |
|
| 165 |
|
- # Application (SPA Vue en mode history) |
|
162 |
+ proxy_pass $target; |
|
163 |
+} |
|
164 |
+ |
|
165 |
+ # Application (SPA Vue en history mode) |
| 166 |
166 |
location / { |
| 167 |
167 |
include /config/nginx/authelia-location.conf; |
| 168 |
168 |
include /config/nginx/proxy.conf; |
| ... |
... |
@@ -169,11 +169,13 @@ |
| 169 |
169 |
include /config/nginx/resolver.conf; |
| 170 |
170 |
proxy_pass $upstream_proto://$upstream_app:$upstream_port; |
| 171 |
171 |
|
| 172 |
|
- # Une 404 de l'upstream sur /login, /settings... renvoie l'index de l'app |
|
172 |
+ # --- Fallback SPA : si l’upstream renvoie 404 sur /login, /settings, etc., |
|
173 |
+ # on renvoie l'index de l'app pour laisser Vue router côté client. |
| 173 |
173 |
proxy_intercept_errors on; |
| 174 |
174 |
error_page 404 = @dashy_fallback; |
| 175 |
175 |
} |
| 176 |
176 |
|
|
178 |
+ # Cible du fallback : renvoyer l'index de l'app en conservant les en-têtes |
| 177 |
177 |
location @dashy_fallback { |
| 178 |
178 |
proxy_set_header X-Original-URI $request_uri; |
| 179 |
179 |
proxy_pass $upstream_proto://$upstream_app:$upstream_port/; |
| ... |
... |
@@ -181,57 +181,41 @@ |
| 181 |
181 |
} |
| 182 |
182 |
{{/code}} |
| 183 |
183 |
|
| 184 |
|
-Fonctionnement du proxy CORS : |
|
186 |
+* Authelia protège tout le domaine, y compris les fichiers YAML, qui contiennent les identifiants des widgets. |
|
187 |
+* ##/cors-proxy## remplace le proxy CORS de Dashy pour les widgets en ##useProxy: true##. Il ne relaie que les en-têtes ##Authorization##, ##Accept## et ##OCS-APIREQUEST##, et refuse les IP privées absentes de ##allowed_internal## : tout nouveau widget qui vise une IP du LAN doit y être ajouté. |
|
188 |
+* ##/pico/## relaie l'interface web du Pico W. |
|
189 |
+* ##@dashy_fallback## renvoie l'application sur les routes internes de Vue (##/login##...), que le serveur de Dashy renvoie en 404. |
| 185 |
185 |
|
| 186 |
|
-* Un widget en ##useProxy: true## n'appelle pas l'API cible depuis le navigateur : il envoie la requête à ##/cors-proxy## sur le domaine de Dashy, avec l'adresse cible dans l'en-tête ##Target-URL## et ses propres en-têtes (authentification comprise) en JSON dans ##CustomHeaders##. |
| 187 |
|
-* Derrière SWAG, c'est ce bloc ##location = /cors-proxy## qui répond, pas le proxy intégré de Dashy. Il ne transmet que ##Authorization##, ##Accept## et ##OCS-APIREQUEST## : un widget qui a besoin d'un autre en-tête doit être ajouté dans le bloc Lua. |
| 188 |
|
-* Les adresses IP privées sont refusées (403), sauf celles de ##allowed_internal##. Tout nouveau widget qui vise une IP du LAN doit y être ajouté, puis SWAG rechargé. Un nom de domaine public passe sans liste blanche. |
| 189 |
|
-* Les appels à ##/cors-proxy## partent de la page Dashy, sur le même domaine : le navigateur y joint le cookie de session Authelia, et ##/cors-proxy## n'est pas utilisable sans connexion. |
| 190 |
|
-* Les requêtes ##OPTIONS## (pré-vol CORS) reçoivent leur 204 avant le contrôle Authelia, car les ##if## de nginx sont évalués avant ##auth_request##. |
| 191 |
|
-* En accès direct sur le port ##4000##, c'est le proxy intégré de Dashy qui répond, sans liste blanche ni Authelia. |
| 192 |
|
-* Nécessite le module Lua de nginx, présent dans l'image SWAG. |
|
191 |
+=== Authelia === |
| 193 |
193 |
|
| 194 |
|
-=== Accès === |
|
193 |
+Règle dans la section ##access_control## de la configuration d'Authelia : |
| 195 |
195 |
|
| 196 |
|
-L'accès se fait en deux étapes : |
| 197 |
|
- |
| 198 |
|
-1. **Authelia**, devant tout le domaine : sans session Authelia, SWAG redirige vers le portail Authelia. Cela couvre l'interface, ##conf.yml## et les pages secondaires, l'API de Dashy, ##/cors-proxy## et ##/pico/##. |
| 199 |
|
-1. **Dashy**, une fois Authelia passé : son écran de connexion donne accès à l'éditeur de configuration au compte administrateur défini dans ##appConfig.auth.users## (##disableConfigurationForNonAdmin: true##). Voir [[Authentification>>||anchor="HAuthentification"]]. |
| 200 |
|
- |
| 201 |
|
-Règle dans la configuration d'[[Authelia>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Authelia.WebHome]], section ##access_control## : |
| 202 |
|
- |
| 203 |
203 |
{{code language="yaml"}} |
| 204 |
204 |
access_control: |
| 205 |
205 |
default_policy: deny |
| 206 |
206 |
rules: |
| 207 |
|
- - domain: "<DOMAINE_DASHY>" |
|
199 |
+ - domain: "dash.<DOMAINE>" |
| 208 |
208 |
policy: one_factor |
| 209 |
209 |
{{/code}} |
| 210 |
210 |
|
| 211 |
|
-* La règle ne sert que si SWAG interroge Authelia : sans les ##include## d'Authelia dans le fichier SWAG, elle n'est jamais évaluée, quelle que soit sa ##policy##. Il faut les deux. |
| 212 |
|
-* Une règle ##bypass## pour ce domaine, placée avant, annulerait la protection : Authelia applique la première règle qui correspond. |
| 213 |
|
-* ##enableGuestAccess: true## ne concerne que les personnes déjà passées par Authelia : elles voient les pages sans se connecter à Dashy, mais ne peuvent pas modifier la configuration. |
| 214 |
|
-* Le fichier Authelia ouvre aussi le CORS de ##/.well-known/openid-configuration## et ##/jwks.json## au domaine de Dashy, en prévision d'une connexion OIDC. Celle-ci n'est pas activée dans ##conf.yml##. |
|
203 |
+Aucune règle ##bypass## pour ce domaine ne doit la précéder : Authelia applique la première règle qui correspond. |
| 215 |
215 |
|
| 216 |
|
-{{warning}}C'est Authelia qui protège les fichiers, pas Dashy : l'authentification de Dashy se fait dans le navigateur et masque seulement l'éditeur. ##conf.yml## et les pages secondaires contiennent les identifiants des widgets, les clés d'API, les adresses internes et le hash du mot de passe administrateur. Toute voie d'accès qui contourne Authelia (port ##4000##, autre proxy, règle ##bypass##) les rend lisibles. Vérifier après chaque modification de SWAG ou d'Authelia, en navigation privée : ##/conf.yml## doit renvoyer vers le portail Authelia.{{/warning}} |
|
205 |
+== Fichiers de configuration == |
| 217 |
217 |
|
| 218 |
|
-== Configuration == |
| 219 |
|
- |
| 220 |
220 |
Dans ##<APPDATA>/Dashy/user-data## : |
| 221 |
221 |
|
| 222 |
|
-|=Élément|=Rôle |
| 223 |
|
-|##conf.yml##|Page d'accueil et configuration commune : ##appConfig##, ##pageInfo##, ##pages##, sections |
| 224 |
|
-|##bookmark.yml##|Page secondaire //bookmarks// : favoris web |
| 225 |
|
-|##cpu.yml##|Page secondaire //cpu// : supervision Glances |
| 226 |
|
-|##news.yml##|Page secondaire //news// : capteurs, flux, actualités, crypto |
| 227 |
|
-|##item-icons/##|Icônes des services et image de fond, stockées en local |
| 228 |
|
-|##conf.yml.bak##, ##bookmark.yml.bak##|Copies manuelles, non utilisées par Dashy |
| 229 |
|
-|##config-backups/##|Dossier de sauvegardes de Dashy (vide actuellement) |
|
209 |
+|=Page Dashy|=Fichier|=Sous-page|=Widgets avec identifiants |
|
210 |
+|Accueil|##conf.yml##|[[Accueil>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Accueil.WebHome]]|##adguard-stats##, ##adguard-filter-status##, ##adguard-top-domains## (compte AdGuard Home) ; ##nextcloud-system## (mot de passe d'application Nextcloud) |
|
211 |
+|bookmarks|##bookmark.yml##|[[Bookmarks>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Bookmarks.WebHome]]|— |
|
212 |
+|cpu|##cpu.yml##|[[CPU>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.CPU.WebHome]]|— |
|
213 |
+|news|##news.yml##|[[News>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.News.WebHome]]|##flight-data## (clé RapidAPI) ; ##news-headlines## (clé Currents API) |
| 230 |
230 |
|
| 231 |
|
-=== Fonctionnement multi-pages === |
|
215 |
+##item-icons/## contient les icônes locales et l'image de fond. |
| 232 |
232 |
|
| 233 |
|
-La page d'accueil est toujours ##conf.yml##. Les autres pages sont déclarées dans sa section ##pages:## : chaque entrée donne le nom affiché dans la barre de navigation et le chemin du fichier, relatif à ##user-data## (une URL distante est aussi acceptée). |
|
217 |
+=== Multi-pages === |
| 234 |
234 |
|
|
219 |
+La page d'accueil est ##conf.yml##. Elle déclare les pages secondaires dans ##pages:## : nom affiché dans la barre de navigation et chemin relatif à ##user-data##. |
|
220 |
+ |
| 235 |
235 |
{{code language="yaml"}} |
| 236 |
236 |
pages: |
| 237 |
237 |
- name: bookmarks |
| ... |
... |
@@ -242,153 +242,23 @@ |
| 242 |
242 |
path: news.yml |
| 243 |
243 |
{{/code}} |
| 244 |
244 |
|
| 245 |
|
-* Une page secondaire ne contient que ##pageInfo## et ##sections## : ##appConfig## (thème, authentification, recherche...) est lu uniquement dans ##conf.yml## et s'applique à toutes les pages. |
| 246 |
|
-* Chaque page secondaire répète la même liste ##pages:##, pour garder la navigation identique d'une page à l'autre. Aucune ne déclare de fichier supplémentaire. |
| 247 |
|
-* Dans ##pageInfo.navLinks## des pages secondaires, un lien ##Home## vers ##/## ramène à l'accueil. |
| 248 |
|
-* Ajouter une page : créer le fichier dans ##user-data##, l'ajouter dans ##pages:## de ##conf.yml## et des autres pages, puis recharger. |
|
231 |
+Chaque page secondaire ne contient que ##pageInfo##, ##sections## et la même liste ##pages:##, pour garder la navigation. ##appConfig## n'est lu que dans ##conf.yml## et s'applique à toutes les pages. |
| 249 |
249 |
|
| 250 |
|
-=== Récapitulatif des pages === |
|
233 |
+Fichiers présents mais non référencés : ##cpu2.yml## (ancienne copie de ##cpu.yml## à l'indentation cassée), ##conf.yml.bak## et ##bookmark.yml.bak##. |
| 251 |
251 |
|
| 252 |
|
-|=Page Dashy|=Fichier|=Sous-page XWiki|=Widgets utilisant des identifiants |
| 253 |
|
-|Accueil|##conf.yml##|[[Accueil>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Accueil.WebHome]]|##adguard-stats##, ##adguard-filter-status##, ##adguard-top-domains## (compte AdGuard Home), ##nextcloud-system## (mot de passe d'application Nextcloud). Les widgets ##image## dépendent du jeton GitHub de github-readme-stats, stocké côté serveur. |
| 254 |
|
-|bookmarks|##bookmark.yml##|[[Bookmarks>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Bookmarks.WebHome]]|Aucun widget |
| 255 |
|
-|cpu|##cpu.yml##|[[CPU>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.CPU.WebHome]]|Aucun (Glances sans authentification) |
| 256 |
|
-|news|##news.yml##|[[News>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.News.WebHome]]|##flight-data## (clé RapidAPI), ##news-headlines## (clé Currents API) |
|
235 |
+=== Éléments communs === |
| 257 |
257 |
|
| 258 |
|
-=== Fichiers non référencés === |
|
237 |
+##appConfig## est dans ##conf.yml##, dont le fichier complet est sur la sous-page [[Accueil>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Accueil.WebHome]]. |
| 259 |
259 |
|
| 260 |
|
-* ##cpu2.yml## : ancienne copie de ##cpu.yml## avec une indentation à un espace qui casse la structure YAML. Déclarée nulle part, mais servie comme les autres fichiers. À supprimer. |
| 261 |
|
-* ##conf.yml.bak## et ##bookmark.yml.bak## : copies de sauvegarde, non déclarées. |
|
239 |
+* Thème ##dracula##, image de fond servie depuis ##item-icons/##. |
|
240 |
+* ##statusCheck: false## au niveau global : la vérification d'état est activée item par item. |
|
241 |
+* ##faviconApi: allesedv## pour les items en ##icon: favicon##. ##enableFontAwesome: true## pour les icônes ##fas##, ##fab## et ##fad##. Les icônes ##hl-...## viennent du jeu homelab intégré. |
|
242 |
+* ##widgetsAlwaysUseProxy: false## : chaque widget choisit avec ##useProxy## s'il passe par ##/cors-proxy##. Le réglage se met sur le widget : au niveau de la section, il est ignoré. |
|
243 |
+* Authentification : un compte ##admin## défini par son nom et le hash SHA-256 de son mot de passe (##echo -n '<MOT_DE_PASSE>' ~| sha256sum##). ##enableGuestAccess: true## : une fois passé Authelia, on voit les pages sans se connecter à Dashy. ##disableConfigurationForNonAdmin: true## : seul l'admin modifie la configuration. |
| 262 |
262 |
|
| 263 |
|
-=== Structure d'un fichier === |
|
245 |
+Vérifier la syntaxe après une modification à la main : |
| 264 |
264 |
|
| 265 |
|
-Un fichier de page contient trois blocs de premier niveau, plus ##appConfig## et ##pages## pour ##conf.yml## : |
| 266 |
|
- |
| 267 |
|
-{{code language="yaml"}} |
| 268 |
|
-pageInfo: |
| 269 |
|
- title: <TITRE> |
| 270 |
|
- description: <SOUS_TITRE> |
| 271 |
|
- navLinks: |
| 272 |
|
- - title: <LIBELLE> |
| 273 |
|
- target: newtab |
| 274 |
|
- path: https://<SITE_PERSO> |
| 275 |
|
-appConfig: |
| 276 |
|
- # conf.yml uniquement, voir ci-dessous |
| 277 |
|
-pages: |
| 278 |
|
- # conf.yml, recopié dans chaque page secondaire |
| 279 |
|
-sections: |
| 280 |
|
- - name: <NOM_SECTION> |
| 281 |
|
- icon: <ICONE> |
| 282 |
|
- displayData: |
| 283 |
|
- sortBy: default # default | alphabetical | random |
| 284 |
|
- rows: 1 |
| 285 |
|
- cols: 1 |
| 286 |
|
- collapsed: false |
| 287 |
|
- hideForGuests: false |
| 288 |
|
- items: |
| 289 |
|
- - title: <NOM_SERVICE> |
| 290 |
|
- description: <DESCRIPTION> |
| 291 |
|
- icon: <ICONE> |
| 292 |
|
- url: https://<DOMAINE_SERVICE>/ |
| 293 |
|
- target: newtab |
| 294 |
|
- statusCheck: true |
| 295 |
|
- statusCheckAllowInsecure: true |
| 296 |
|
- id: <ID> |
| 297 |
|
- widgets: |
| 298 |
|
- - type: <TYPE_WIDGET> |
| 299 |
|
- useProxy: true |
| 300 |
|
- options: |
| 301 |
|
- <OPTION>: <VALEUR> |
| 302 |
|
- id: <ID> |
| 303 |
|
-{{/code}} |
| 304 |
|
- |
| 305 |
|
-* ##id## : identifiant unique généré par l'interface. Facultatif en écriture manuelle : Dashy en génère un s'il manque. |
| 306 |
|
-* ##icon## : formes utilisées dans cette configuration : |
| 307 |
|
-** nom de fichier présent dans ##item-icons/## (##portainer.svg##) ; |
| 308 |
|
-** ##hl-<nom>## : jeu d'icônes homelab intégré (##hl-jellyfin##) ; |
| 309 |
|
-** classes Font Awesome (##fas fa-terminal##, ##fab fa-github##, ##fad fa-clock##), actif via ##enableFontAwesome: true## ; |
| 310 |
|
-** ##favicon## : favicon du site récupéré par le service défini dans ##faviconApi## ; |
| 311 |
|
-** emoji (##🛡️##) ou URL complète d'une image. |
| 312 |
|
-* ##useProxy## se place sur le widget, pas sur la section : placé au niveau de la section, il est ignoré. |
| 313 |
|
- |
| 314 |
|
-=== appConfig et thème === |
| 315 |
|
- |
| 316 |
|
-{{code language="yaml"}} |
| 317 |
|
-appConfig: |
| 318 |
|
- theme: dracula |
| 319 |
|
- layout: auto |
| 320 |
|
- iconSize: small |
| 321 |
|
- backgroundImg: /item-icons/<IMAGE_FOND> |
| 322 |
|
- startingView: default |
| 323 |
|
- defaultOpeningMethod: newtab |
| 324 |
|
- statusCheck: false |
| 325 |
|
- statusCheckInterval: 0 |
| 326 |
|
- faviconApi: allesedv |
| 327 |
|
- enableFontAwesome: true |
| 328 |
|
- enableMaterialDesignIcons: false |
| 329 |
|
- widgetsAlwaysUseProxy: false |
| 330 |
|
- webSearch: |
| 331 |
|
- searchEngine: duckduckgo |
| 332 |
|
- openingMethod: newtab |
| 333 |
|
- searchBangs: {} |
| 334 |
|
- disableWebSearch: false |
| 335 |
|
- auth: |
| 336 |
|
- enableGuestAccess: true |
| 337 |
|
- users: |
| 338 |
|
- - user: <UTILISATEUR_DASHY> |
| 339 |
|
- hash: <HASH_SHA256_DU_MOT_DE_PASSE> |
| 340 |
|
- type: admin |
| 341 |
|
- enableHeaderAuth: false |
| 342 |
|
- headerAuth: |
| 343 |
|
- userHeader: REMOTE_USER |
| 344 |
|
- proxyWhitelist: [] |
| 345 |
|
- enableKeycloak: false |
| 346 |
|
- hideComponents: |
| 347 |
|
- hideHeading: false |
| 348 |
|
- hideNav: false |
| 349 |
|
- hideSearch: false |
| 350 |
|
- hideSettings: false |
| 351 |
|
- hideFooter: false |
| 352 |
|
- showSplashScreen: false |
| 353 |
|
- preventWriteToDisk: false |
| 354 |
|
- preventLocalSave: false |
| 355 |
|
- disableConfiguration: false |
| 356 |
|
- disableConfigurationForNonAdmin: true |
| 357 |
|
- allowConfigEdit: true |
| 358 |
|
- enableServiceWorker: false |
| 359 |
|
- disableContextMenu: false |
| 360 |
|
- disableUpdateChecks: false |
| 361 |
|
- disableSmartSort: false |
| 362 |
|
- enableErrorReporting: false |
| 363 |
|
-{{/code}} |
| 364 |
|
- |
| 365 |
|
-|=Clé|=Effet |
| 366 |
|
-|##theme: dracula##|Thème sombre intégré, appliqué à toutes les pages |
| 367 |
|
-|##backgroundImg##|Image de fond, servie depuis ##item-icons/## |
| 368 |
|
-|##statusCheck: false##|Pas de vérification d'état globale : elle est activée item par item (##statusCheck: true##) |
| 369 |
|
-|##faviconApi: allesedv##|Les icônes ##favicon## sont récupérées via le service externe allesedv |
| 370 |
|
-|##widgetsAlwaysUseProxy: false##|Chaque widget choisit avec ##useProxy## s'il passe par ##/cors-proxy## |
| 371 |
|
-|##webSearch##|Recherche web DuckDuckGo depuis la barre de recherche, en nouvel onglet |
| 372 |
|
-|##preventWriteToDisk: false##|L'interface peut écrire dans les fichiers YAML du volume |
| 373 |
|
-|##preventLocalSave: false##|Les modifications peuvent aussi être gardées dans le stockage local du navigateur |
| 374 |
|
-|##disableConfigurationForNonAdmin: true##|Seul un compte ##type: admin## voit l'éditeur de configuration |
| 375 |
|
- |
| 376 |
|
-=== Authentification === |
| 377 |
|
- |
| 378 |
|
-* Un seul compte, administrateur, défini par son nom et le hash SHA-256 de son mot de passe. Générer le hash : |
| 379 |
|
- |
| 380 |
380 |
{{code language="bash"}} |
| 381 |
|
-echo -n '<MOT_DE_PASSE_DASHY>' | sha256sum |
| 382 |
|
-{{/code}} |
| 383 |
|
- |
| 384 |
|
-* ##enableGuestAccess: true## : les visiteurs authentifiés par Authelia mais pas par Dashy voient toutes les pages. ##hideForGuests: true## sur une section la masque aux invités, sans la retirer des fichiers servis. |
| 385 |
|
-* ##enableHeaderAuth## (utilisateur transmis par le reverse proxy dans ##REMOTE_USER##) et ##enableKeycloak## sont désactivés. |
| 386 |
|
- |
| 387 |
|
-=== Vérifier la syntaxe === |
| 388 |
|
- |
| 389 |
|
-Après une modification à la main : |
| 390 |
|
- |
| 391 |
|
-{{code language="bash"}} |
| 392 |
392 |
docker exec dashy yarn validate-config |
| 393 |
393 |
{{/code}} |
| 394 |
394 |
|
| ... |
... |
@@ -416,11 +416,11 @@ |
| 416 |
416 |
Principales adresses : |
| 417 |
417 |
|
| 418 |
418 |
|=Carte|=Adresse |
| 419 |
|
-|Statistiques du profil|##/api?username=<UTILISATEUR_GITHUB>## |
| 420 |
|
-|Langages les plus utilisés|##/api/top-langs?username=<UTILISATEUR_GITHUB>## |
| 421 |
|
-|Dépôt épinglé|##/api/pin?username=<UTILISATEUR_GITHUB>&repo=<DEPOT>## |
|
275 |
+|Statistiques du profil|##/api?username=<UTILISATEUR>## |
|
276 |
+|Langages les plus utilisés|##/api/top-langs?username=<UTILISATEUR>## |
|
277 |
+|Dépôt épinglé|##/api/pin?username=<UTILISATEUR>&repo=<DEPOT>## |
| 422 |
422 |
|
| 423 |
|
-Dans Dashy, les cartes s'affichent avec un widget de type ##image## qui pointe vers l'instance locale (voir la sous-page [[Accueil>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Accueil.WebHome]]). |
|
279 |
+Dans Dashy, les cartes s'affichent avec un widget de type ##image## qui pointe vers l'instance locale. |
| 424 |
424 |
|
| 425 |
425 |
=== Image locale === |
| 426 |
426 |
|
| ... |
... |
@@ -435,49 +435,34 @@ |
| 435 |
435 |
|
| 436 |
436 |
== Maintenance == |
| 437 |
437 |
|
| 438 |
|
-=== Après une migration ou une réinstallation d'un service === |
|
294 |
+=== Après une migration ou une réinstallation === |
| 439 |
439 |
|
| 440 |
|
-|=Service migré ou réinstallé|=À refaire dans Dashy |
| 441 |
|
-|Nextcloud|Créer un nouveau mot de passe d'application et le reporter dans ##nextcloud-system## : les mots de passe d'application ne suivent pas une migration (sauvegarde restaurée, passage à AIO, nouvelle instance). Vérifier que le compte est toujours administrateur et que le nom d'utilisateur a gardé la même casse. |
| 442 |
|
-|AdGuard Home|Reporter le nom d'utilisateur et le mot de passe de l'interface web dans les trois widgets ##adguard-*##. Si l'adresse IP change : mettre à jour ##hostname## et la liste ##allowed_internal## de SWAG. |
| 443 |
|
-|Glances|Mettre à jour ##hostname## dans tous les widgets ##gl-*## de ##cpu.yml## si le domaine change. |
| 444 |
|
-|github-readme-stats|Reconstruire l'image locale et recréer le jeton ##PAT_1## s'il a expiré. |
| 445 |
|
-|Pico W|Mettre à jour son IP dans ##news.yml## (item et script du widget ##embed##), dans ##location /pico/## et dans ##allowed_internal## de SWAG. |
| 446 |
|
-|SWAG|Recopier ##30-dashy.subdomain.conf## complet. Sans les ##include## d'Authelia, Dashy et ses fichiers de configuration redeviennent publics. Sans le bloc ##/cors-proxy##, les widgets en ##useProxy: true## passent par le proxy de Dashy, sans liste blanche. |
| 447 |
|
-|Authelia|Vérifier que la règle ##one_factor## du domaine de Dashy est toujours présente et qu'aucune règle ##bypass## ne la précède. |
| 448 |
|
-|Dashy|Restaurer ##user-data## (fichiers YAML et ##item-icons/##). Aucun identifiant n'est stocké ailleurs. |
| 449 |
|
-|API externes (RapidAPI, Currents)|Si la clé est régénérée ou le compte recréé, reporter la nouvelle clé dans ##news.yml##. |
|
296 |
+|=Service|=À refaire |
|
297 |
+|Nextcloud|Créer un nouveau mot de passe d'application et le reporter dans ##nextcloud-system## : les mots de passe d'application ne suivent pas une migration. |
|
298 |
+|AdGuard Home|Reporter le compte de l'interface web dans les trois widgets ##adguard-*##. Si l'IP change : ##hostname## et ##allowed_internal## de SWAG. |
|
299 |
+|Pico W|Si l'IP change : ##news.yml## (item et script), ##location /pico/## et ##allowed_internal## de SWAG. |
|
300 |
+|SWAG, Authelia|Remettre ##30-dashy.subdomain.conf## complet et la règle ##one_factor##, puis vérifier en navigation privée que ##/conf.yml## redirige vers Authelia. |
|
301 |
+|Dashy|Restaurer ##user-data##. |
| 450 |
450 |
|
| 451 |
451 |
=== Recharger la configuration === |
| 452 |
452 |
|
| 453 |
|
-* Modification depuis l'interface : l'enregistrement sur le disque, depuis l'éditeur de configuration, écrit le fichier YAML de la page affichée. |
| 454 |
|
-* Modification à la main : Dashy relit les fichiers YAML à chaque chargement, un rafraîchissement du navigateur suffit. Si l'ancienne configuration reste affichée, elle vient du stockage local du navigateur : réinitialiser les paramètres locaux depuis le menu de configuration, ou vider les données du site. |
| 455 |
|
-* Changement dans la stack (variables d'environnement, image) : redéployer la stack dans Portainer. |
| 456 |
|
-* Changement dans le fichier SWAG : ##docker exec swag nginx -t## puis ##docker exec swag nginx -s reload##. |
| 457 |
|
-* Changement dans la configuration d'Authelia : ##docker restart authelia##. |
|
305 |
+* Dashy relit les fichiers YAML à chaque chargement : après une modification à la main, rafraîchir la page. Si l'ancienne version reste affichée, elle vient du stockage local du navigateur : réinitialiser les paramètres locaux depuis le menu de configuration. |
|
306 |
+* SWAG : ##docker exec swag nginx -t## puis ##docker exec swag nginx -s reload##. Authelia : ##docker restart authelia##. |
| 458 |
458 |
|
| 459 |
459 |
=== Un widget reste vide === |
| 460 |
460 |
|
| 461 |
|
-Dans l'ordre : |
|
310 |
+Dans les outils de développement du navigateur (F12, onglet Réseau), regarder la requête du widget : |
| 462 |
462 |
|
| 463 |
|
-1. Outils de développement du navigateur (F12), onglets Console et Réseau : repérer la requête du widget et son code de retour. |
| 464 |
|
-1. Requête vers ##/cors-proxy## : |
| 465 |
|
-1*. 302 vers le portail Authelia, ou 401 : session Authelia expirée ; recharger la page et se reconnecter ; |
| 466 |
|
-1*. 400 ##Target-URL invalide## : ##hostname## mal formé (schéma ##http:~/~/## ou ##https:~/~/## obligatoire) ; |
| 467 |
|
-1*. 403 ##Cible interne non autorisée## : IP du LAN absente de ##allowed_internal## dans SWAG ; |
| 468 |
|
-1*. 401 ou 403 renvoyé par la cible : identifiants faux, mot de passe d'application révoqué, compte sans droits suffisants, ou en-tête d'authentification non transmis par le bloc Lua ; |
| 469 |
|
-1*. 502 ou 504 : service cible arrêté ou injoignable depuis le conteneur SWAG. |
| 470 |
|
-1. Requête directe (sans ##/cors-proxy##) bloquée avec une erreur CORS dans la console : passer le widget en ##useProxy: true##. |
| 471 |
|
-1. Journaux : ##docker logs swag## et ##<APPDATA>/swag/config/log/nginx/error.log## pour le proxy, ##docker logs dashy## pour le serveur de Dashy. |
| 472 |
|
-1. Vérifier la syntaxe du fichier avec ##yarn validate-config## : une erreur d'indentation peut vider toute une section. |
| 473 |
|
-1. Tester l'API cible à la main depuis l'hôte, avec les mêmes identifiants. Exemple pour Nextcloud : |
|
312 |
+|=Symptôme|=Cause |
|
313 |
+|Erreur CORS sur un appel direct|Passer le widget en ##useProxy: true## |
|
314 |
+|##/cors-proxy## : 302 ou 401|Session Authelia expirée : recharger la page |
|
315 |
+|##/cors-proxy## : 403 ##Cible interne non autorisée##|IP absente de ##allowed_internal## |
|
316 |
+|##/cors-proxy## : 401 ou 403 de la cible|Identifiants faux ou révoqués, compte sans droits, ou en-tête non relayé par le bloc Lua |
|
317 |
+|##/cors-proxy## : 502 ou 504|Service cible arrêté ou injoignable depuis SWAG |
| 474 |
474 |
|
| 475 |
|
-{{code language="bash"}} |
| 476 |
|
-curl -u '<UTILISATEUR_NEXTCLOUD>:<MOT_DE_PASSE_APPLICATION>' -H 'OCS-APIREQUEST: true' \ |
| 477 |
|
- 'https://<DOMAINE_NEXTCLOUD>/ocs/v2.php/apps/serverinfo/api/v1/info?format=json' |
| 478 |
|
-{{/code}} |
|
319 |
+Journaux : ##docker logs dashy## et ##<APPDATA>/swag/config/log/nginx/error.log##. |
| 479 |
479 |
|
| 480 |
480 |
== Sauvegarde == |
| 481 |
481 |
|
| 482 |
|
-##<APPDATA>/Dashy/user-data## est sauvegardé par le script de [[Sauvegarde>>Wiki Batcave John.Infrastructure.Sauvegarde.WebHome]]. Il contient toute la configuration, identifiants des widgets compris : la sauvegarde doit être protégée en conséquence. Le fichier SWAG ##30-dashy.subdomain.conf## est aussi dans ##<APPDATA>##, couvert par la même sauvegarde. |
|
323 |
+##<APPDATA>/Dashy/user-data## est sauvegardé par le script de [[Sauvegarde>>Wiki Batcave John.Infrastructure.Sauvegarde.WebHome]], identifiants des widgets compris. Le fichier SWAG est aussi dans ##<APPDATA>##. |
| 483 |
483 |
|