| ... |
... |
@@ -1,14 +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. |
|
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. |
| 6 |
|
-* Indicateur d'état de chaque service (vérification de disponibilité). |
| 7 |
|
-* Widgets de contenu dynamique, dont les cartes de statistiques GitHub fournies par une instance locale de github-readme-stats. |
| 8 |
|
-* Configuration modifiable depuis l'interface, enregistrée dans le fichier 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. |
| 9 |
9 |
|
| 10 |
|
-== Stack == |
|
7 |
+== Déploiement == |
| 11 |
11 |
|
|
9 |
+=== Stack Portainer === |
|
10 |
+ |
| 12 |
12 |
{{code language="yaml"}} |
| 13 |
13 |
services: |
| 14 |
14 |
dashy: |
| ... |
... |
@@ -19,7 +19,6 @@ |
| 19 |
19 |
- UID=1000 |
| 20 |
20 |
- GID=1000 |
| 21 |
21 |
- NODE_TLS_REJECT_UNAUTHORIZED=0 |
| 22 |
|
- - VUE_APP_BOB=<MOT_DE_PASSE> |
| 23 |
23 |
ports: |
| 24 |
24 |
- "4000:8080" |
| 25 |
25 |
volumes: |
| ... |
... |
@@ -39,30 +39,217 @@ |
| 39 |
39 |
external: true |
| 40 |
40 |
{{/code}} |
| 41 |
41 |
|
| 42 |
|
-* ##NODE_TLS_REJECT_UNAUTHORIZED=0## : le serveur de Dashy accepte les certificats non reconnus lors des vérifications d'état, nécessaire pour les services locaux en https avec un certificat auto-signé. |
| 43 |
|
-* ##VUE_APP_BOB## : mot de passe d'un utilisateur de l'authentification intégrée, référencé par son nom dans ##conf.yml## (##password: VUE_APP_BOB##). Il est lu à la construction de l'application : après modification, reconstruire (Config > Rebuild). |
|
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##. |
| 44 |
44 |
|
| 45 |
|
-=== Accès === |
|
44 |
+=== SWAG === |
| 46 |
46 |
|
| 47 |
|
-* Consultation : publique, sans authentification. |
| 48 |
|
-* Modification de la configuration : connexion en deux temps, d'abord Authelia, qui redirige ensuite vers la connexion de Dashy (utilisateur défini dans ##conf.yml##, mot de passe dans ##VUE_APP_BOB##). |
| 49 |
|
-{{info}}En consultation publique, le contenu de ##conf.yml## reste lisible par tous : n'y mettre ni mot de passe, ni jeton, ni adresse de service qu'on ne veut pas rendre visible.{{/info}} |
|
46 |
+##<APPDATA>/swag/config/nginx/proxy-confs/30-dashy.subdomain.conf## : |
| 50 |
50 |
|
| 51 |
|
-== Configuration == |
|
48 |
+{{code language="nginx"}} |
|
49 |
+server { |
|
50 |
+ listen 443 ssl; |
|
51 |
+ listen [::]:443 ssl; |
|
52 |
+ server_name dash.<DOMAINE>; |
| 52 |
52 |
|
|
54 |
+ include /config/nginx/ssl.conf; |
|
55 |
+ include /config/nginx/snippets/robots-default.conf; |
|
56 |
+ |
|
57 |
+ client_max_body_size 25m; |
|
58 |
+ |
|
59 |
+ # Authelia devant tout le domaine |
|
60 |
+ include /config/nginx/authelia-server.conf; |
|
61 |
+ |
|
62 |
+ # Upstream Dashy |
|
63 |
+ set $upstream_app dashy; |
|
64 |
+ set $upstream_port 8080; |
|
65 |
+ set $upstream_proto http; |
|
66 |
+ |
|
67 |
+ # (facultatif) proxy vers /pico/ |
|
68 |
+ location /pico/ { |
|
69 |
+ include /config/nginx/authelia-location.conf; |
|
70 |
+ proxy_pass http://<IP_PICO>/; |
|
71 |
+ include /config/nginx/proxy.conf; |
|
72 |
+ proxy_set_header Connection close; |
|
73 |
+ } |
|
74 |
+ |
|
75 |
+ # API de Dashy (sauvegarde de la config, vérifications d'état) |
|
76 |
+ location ^~ /api/ { |
|
77 |
+ include /config/nginx/authelia-location.conf; |
|
78 |
+ include /config/nginx/proxy.conf; |
|
79 |
+ include /config/nginx/resolver.conf; |
|
80 |
+ proxy_pass $upstream_proto://$upstream_app:$upstream_port; |
|
81 |
+ } |
|
82 |
+ location = /cors-proxy { |
|
83 |
+ include /config/nginx/authelia-location.conf; |
|
84 |
+ # Dashy envoie la cible dans ce header |
|
85 |
+ set $target $http_target_url; |
|
86 |
+ if ($target = "") { return 400; } |
|
87 |
+ |
|
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; } |
|
92 |
+ |
|
93 |
+ # TLS (certs auto-signés possibles sur les cibles LAN, ex: Nextcloud) |
|
94 |
+ proxy_ssl_server_name on; |
|
95 |
+ proxy_ssl_verify off; |
|
96 |
+ |
|
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 ""; |
|
102 |
+ |
|
103 |
+ access_by_lua_block { |
|
104 |
+ local target = ngx.var.target |
|
105 |
+ |
|
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 |
|
113 |
+ |
|
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 |
|
127 |
+ |
|
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 |
|
135 |
+ |
|
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"]) |
|
149 |
+ end |
|
150 |
+ end |
|
151 |
+ end |
|
152 |
+ } |
|
153 |
+ |
|
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; |
|
158 |
+ |
|
159 |
+ proxy_set_header Host $host_override; |
|
160 |
+ proxy_set_header X-Forwarded-Proto https; |
|
161 |
+ |
|
162 |
+ proxy_pass $target; |
|
163 |
+} |
|
164 |
+ |
|
165 |
+ # Application (SPA Vue en history mode) |
|
166 |
+ location / { |
|
167 |
+ include /config/nginx/authelia-location.conf; |
|
168 |
+ include /config/nginx/proxy.conf; |
|
169 |
+ include /config/nginx/resolver.conf; |
|
170 |
+ proxy_pass $upstream_proto://$upstream_app:$upstream_port; |
|
171 |
+ |
|
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. |
|
174 |
+ proxy_intercept_errors on; |
|
175 |
+ error_page 404 = @dashy_fallback; |
|
176 |
+ } |
|
177 |
+ |
|
178 |
+ # Cible du fallback : renvoyer l'index de l'app en conservant les en-têtes |
|
179 |
+ location @dashy_fallback { |
|
180 |
+ proxy_set_header X-Original-URI $request_uri; |
|
181 |
+ proxy_pass $upstream_proto://$upstream_app:$upstream_port/; |
|
182 |
+ } |
|
183 |
+} |
|
184 |
+{{/code}} |
|
185 |
+ |
|
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. |
|
190 |
+ |
|
191 |
+=== Authelia === |
|
192 |
+ |
|
193 |
+Règle dans la section ##access_control## de la configuration d'Authelia : |
|
194 |
+ |
|
195 |
+{{code language="yaml"}} |
|
196 |
+access_control: |
|
197 |
+ default_policy: deny |
|
198 |
+ rules: |
|
199 |
+ - domain: "dash.<DOMAINE>" |
|
200 |
+ policy: one_factor |
|
201 |
+{{/code}} |
|
202 |
+ |
|
203 |
+Aucune règle ##bypass## pour ce domaine ne doit la précéder : Authelia applique la première règle qui correspond. |
|
204 |
+ |
|
205 |
+== Fichiers de configuration == |
|
206 |
+ |
| 53 |
53 |
Dans ##<APPDATA>/Dashy/user-data## : |
| 54 |
54 |
|
| 55 |
|
-|=Élément|=Rôle |
| 56 |
|
-|##conf.yml##|Configuration principale : pages, sections, liens, widgets, thème |
| 57 |
|
-|##bookmark.yml##|Page secondaire de favoris |
| 58 |
|
-|##item-icons/##|Icônes des services, stockées en local |
|
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) |
| 59 |
59 |
|
| 60 |
|
-* ##appConfig.faviconApi: local## : aucune icône n'est récupérée sur un service externe. |
| 61 |
|
-* Vérifier la syntaxe après une modification à la main : |
|
215 |
+##item-icons/## contient les icônes locales et l'image de fond. |
| 62 |
62 |
|
|
217 |
+=== Multi-pages === |
|
218 |
+ |
|
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 |
+ |
|
221 |
+{{code language="yaml"}} |
|
222 |
+pages: |
|
223 |
+ - name: bookmarks |
|
224 |
+ path: bookmark.yml |
|
225 |
+ - name: cpu |
|
226 |
+ path: cpu.yml |
|
227 |
+ - name: news |
|
228 |
+ path: news.yml |
|
229 |
+{{/code}} |
|
230 |
+ |
|
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. |
|
232 |
+ |
|
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##. |
|
234 |
+ |
|
235 |
+=== Éléments communs === |
|
236 |
+ |
|
237 |
+##appConfig## est dans ##conf.yml##, dont le fichier complet est sur la sous-page [[Accueil>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Accueil.WebHome]]. |
|
238 |
+ |
|
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. |
|
244 |
+ |
|
245 |
+Vérifier la syntaxe après une modification à la main : |
|
246 |
+ |
| 63 |
63 |
{{code language="bash"}} |
| 64 |
64 |
docker exec dashy yarn validate-config |
| 65 |
65 |
{{/code}} |
|
250 |
+ |
| 66 |
66 |
== Statistiques GitHub : github-readme-stats == |
| 67 |
67 |
|
| 68 |
68 |
Instance locale de github-readme-stats, qui génère les cartes SVG de statistiques GitHub affichées par un widget de Dashy : statistiques du profil, langages les plus utilisés, dépôts épinglés. |
| ... |
... |
@@ -104,6 +104,36 @@ |
| 104 |
104 |
|
| 105 |
105 |
Puis redéployer la stack dans Portainer. |
| 106 |
106 |
|
|
292 |
+== Maintenance == |
|
293 |
+ |
|
294 |
+=== Après une migration ou une réinstallation === |
|
295 |
+ |
|
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##. |
|
302 |
+ |
|
303 |
+=== Recharger la configuration === |
|
304 |
+ |
|
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##. |
|
307 |
+ |
|
308 |
+=== Un widget reste vide === |
|
309 |
+ |
|
310 |
+Dans les outils de développement du navigateur (F12, onglet Réseau), regarder la requête du widget : |
|
311 |
+ |
|
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 |
|
318 |
+ |
|
319 |
+Journaux : ##docker logs dashy## et ##<APPDATA>/swag/config/log/nginx/error.log##. |
|
320 |
+ |
| 107 |
107 |
== Sauvegarde == |
| 108 |
108 |
|
| 109 |
|
-##<APPDATA>/Dashy/user-data## est sauvegardé par le script de [[Sauvegarde>>Wiki Batcave John.Infrastructure.Sauvegarde.WebHome]]. Dashy enregistre aussi une copie de la configuration précédente dans ce dossier à chaque sauvegarde depuis l'interface. |
|
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>##. |
|
324 |
+ |