0 Votes

Code source wiki de Dashy

Modifié par john le 2026/09/26 16:54

Masquer les derniers auteurs
john 5.1 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.
john 1.1 2
john 5.1 3 {{toc/}}
john 1.1 4
john 5.1 5 Les valeurs propres à l'infrastructure sont remplacées par des marqueurs ##<ENTRE_CHEVRONS>## : ##<DOMAINE>##, adresses IP, comptes, mots de passe et clés.
john 1.1 6
john 5.1 7 == Déploiement ==
john 1.1 8
john 5.1 9 === Stack Portainer ===
10
john 1.1 11 {{code language="yaml"}}
12 services:
13 dashy:
14 image: lissy93/dashy:latest
15 container_name: dashy
16 environment:
17 - NODE_ENV=production
18 - UID=1000
19 - GID=1000
20 - NODE_TLS_REJECT_UNAUTHORIZED=0
21 ports:
22 - "4000:8080"
23 volumes:
24 - <APPDATA>/Dashy/user-data:/app/user-data
25 restart: unless-stopped
26 healthcheck:
27 test: ['CMD', 'node', '/app/services/healthcheck']
28 interval: 90s
29 timeout: 10s
30 retries: 3
31 start_period: 40s
32 networks:
33 - proxy
34
35 networks:
36 proxy:
37 external: true
38 {{/code}}
39
john 5.1 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##.
john 1.1 43
john 5.1 44 === SWAG ===
john 4.1 45
john 5.1 46 ##<APPDATA>/swag/config/nginx/proxy-confs/30-dashy.subdomain.conf## :
john 4.1 47
48 {{code language="nginx"}}
49 server {
50 listen 443 ssl;
51 listen [::]:443 ssl;
john 5.1 52 server_name dash.<DOMAINE>;
john 4.1 53
54 include /config/nginx/ssl.conf;
55 include /config/nginx/snippets/robots-default.conf;
56
57 client_max_body_size 25m;
58
john 5.1 59 # Authelia devant tout le domaine
john 4.1 60 include /config/nginx/authelia-server.conf;
61
john 5.1 62 # Upstream Dashy
john 4.1 63 set $upstream_app dashy;
64 set $upstream_port 8080;
65 set $upstream_proto http;
66
john 5.1 67 # (facultatif) proxy vers /pico/
john 4.1 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
john 5.1 75 # API de Dashy (sauvegarde de la config, vérifications d'état)
john 4.1 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;
john 5.1 84 # Dashy envoie la cible dans ce header
85 set $target $http_target_url;
86 if ($target = "") { return 400; }
john 4.1 87
john 5.1 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; }
john 4.1 92
john 5.1 93 # TLS (certs auto-signés possibles sur les cibles LAN, ex: Nextcloud)
94 proxy_ssl_server_name on;
95 proxy_ssl_verify off;
john 4.1 96
john 5.1 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 "";
john 4.1 102
john 5.1 103 access_by_lua_block {
104 local target = ngx.var.target
john 4.1 105
john 5.1 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
john 4.1 113
john 5.1 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
john 4.1 127
john 5.1 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
john 4.1 135
john 5.1 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"])
john 4.1 149 end
john 5.1 150 end
151 end
152 }
john 4.1 153
john 5.1 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;
john 4.1 158
john 5.1 159 proxy_set_header Host $host_override;
160 proxy_set_header X-Forwarded-Proto https;
john 4.1 161
john 5.1 162 proxy_pass $target;
163 }
164
165 # Application (SPA Vue en history mode)
john 4.1 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
john 5.1 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.
john 4.1 174 proxy_intercept_errors on;
175 error_page 404 = @dashy_fallback;
176 }
177
john 5.1 178 # Cible du fallback : renvoyer l'index de l'app en conservant les en-têtes
john 4.1 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
john 5.1 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.
john 4.1 190
john 5.1 191 === Authelia ===
john 4.1 192
john 5.1 193 Règle dans la section ##access_control## de la configuration d'Authelia :
john 1.1 194
john 4.1 195 {{code language="yaml"}}
196 access_control:
197 default_policy: deny
198 rules:
john 5.1 199 - domain: "dash.<DOMAINE>"
john 4.1 200 policy: one_factor
201 {{/code}}
202
john 5.1 203 Aucune règle ##bypass## pour ce domaine ne doit la précéder : Authelia applique la première règle qui correspond.
john 4.1 204
john 5.1 205 == Fichiers de configuration ==
john 4.1 206
john 1.1 207 Dans ##<APPDATA>/Dashy/user-data## :
208
john 5.1 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)
john 1.1 214
john 5.1 215 ##item-icons/## contient les icônes locales et l'image de fond.
john 1.1 216
john 5.1 217 === Multi-pages ===
john 4.1 218
john 5.1 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
john 4.1 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
john 5.1 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.
john 4.1 232
john 5.1 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##.
john 4.1 234
john 5.1 235 === Éléments communs ===
john 4.1 236
john 5.1 237 ##appConfig## est dans ##conf.yml##, dont le fichier complet est sur la sous-page [[Accueil>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Dashy.Accueil.WebHome]].
john 4.1 238
john 5.1 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.
john 4.1 244
john 5.1 245 Vérifier la syntaxe après une modification à la main :
john 4.1 246
john 1.1 247 {{code language="bash"}}
248 docker exec dashy yarn validate-config
249 {{/code}}
john 4.1 250
john 3.1 251 == Statistiques GitHub : github-readme-stats ==
john 1.1 252
john 3.1 253 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.
254
255 {{code language="yaml"}}
256 services:
257 github-readme-stats:
258 image: github-readme-stats:local
259 container_name: github-readme-stats
260 restart: unless-stopped
261 ports:
262 - "9000:9000"
263 environment:
264 - PAT_1=${PAT_1}
265 - CACHE_SECONDS=14400
266 {{/code}}
267
268 * ##PAT_1## : jeton d'accès personnel GitHub, défini dans les variables de la stack Portainer. Jeton classique avec les permissions ##repo## pour compter les dépôts privés.
269 * ##CACHE_SECONDS## : durée de mise en cache des cartes (4 heures), pour limiter les appels à l'API GitHub.
270 * Accessible uniquement en local, sur le port 9000.
271
272 Principales adresses :
273
274 |=Carte|=Adresse
john 5.1 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>##
john 3.1 278
john 5.1 279 Dans Dashy, les cartes s'affichent avec un widget de type ##image## qui pointe vers l'instance locale.
john 3.1 280
281 === Image locale ===
282
283 Portainer ne sait pas construire une image dans une stack : l'image ##github-readme-stats:local## est construite sur l'hôte, depuis le dépôt cloné. Elle n'est donc pas mise à jour par Watchtower. Pour la mettre à jour, depuis le dossier du dépôt :
284
285 {{code language="bash"}}
286 git pull
287 docker build -t github-readme-stats:local .
288 {{/code}}
289
290 Puis redéployer la stack dans Portainer.
291
john 4.1 292 == Maintenance ==
293
john 5.1 294 === Après une migration ou une réinstallation ===
john 4.1 295
john 5.1 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##.
john 4.1 302
303 === Recharger la configuration ===
304
john 5.1 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##.
john 4.1 307
308 === Un widget reste vide ===
309
john 5.1 310 Dans les outils de développement du navigateur (F12, onglet Réseau), regarder la requête du widget :
john 4.1 311
john 5.1 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
john 4.1 318
john 5.1 319 Journaux : ##docker logs dashy## et ##<APPDATA>/swag/config/log/nginx/error.log##.
john 4.1 320
john 1.1 321 == Sauvegarde ==
322
john 5.1 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>##.
john 4.1 324