chore: état initial avant refonte (atlas SvelteKit v4.0.0)
Snapshot de l'existant avant application du playbook OKI : background shader Three.js, carte graphe 3D, timeline statique.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/* Génère les fichiers de configuration CSP du déploiement statique :
|
||||
- build/nginx-csp.conf → snippet à inclure dans la config nginx de l'app
|
||||
YunoHost (cible de production : https://syel.o-k-i.net).
|
||||
- build/.htaccess → équivalent Apache (secours, ignoré par nginx).
|
||||
|
||||
Pourquoi : SvelteKit démarre l'hydratation via un script inline dans le
|
||||
HTML. Une CSP stricte sans « unsafe-inline » (ex. script-src 'self')
|
||||
bloque ce bootstrap → site visible mais non interactif. Les empreintes
|
||||
SHA-256 des scripts inline l'autorisent précisément, sans unsafe-inline.
|
||||
Régénéré à chaque build (les empreintes changent avec les chunks). */
|
||||
|
||||
import { createHash } from 'node:crypto';
|
||||
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const buildDir = fileURLToPath(new URL('../build/', import.meta.url));
|
||||
|
||||
function* walkHtml(dir) {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const full = join(dir, entry);
|
||||
if (statSync(full).isDirectory()) yield* walkHtml(full);
|
||||
else if (entry.endsWith('.html')) yield full;
|
||||
}
|
||||
}
|
||||
|
||||
const hashes = new Set();
|
||||
|
||||
for (const file of walkHtml(buildDir)) {
|
||||
const html = readFileSync(file, 'utf8');
|
||||
// Scripts inline uniquement (pas d'attribut src) — le navigateur empreinte
|
||||
// le contenu texte exact du bloc.
|
||||
for (const match of html.matchAll(/<script(?![^>]*\bsrc\s*=)[^>]*>([\s\S]*?)<\/script>/g)) {
|
||||
const content = match[1];
|
||||
if (!content.trim()) continue;
|
||||
const digest = createHash('sha256').update(content, 'utf8').digest('base64');
|
||||
hashes.add(`'sha256-${digest}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if (hashes.size === 0) {
|
||||
console.warn('postbuild-csp: aucun script inline trouvé — vérifier le build');
|
||||
}
|
||||
|
||||
// Politique sobre pour un site statique autohébergé, + empreintes du bootstrap.
|
||||
const csp = [
|
||||
"default-src 'self'",
|
||||
`script-src 'self' ${[...hashes].sort().join(' ')}`,
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self'",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self'",
|
||||
"manifest-src 'self'",
|
||||
"worker-src 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'",
|
||||
"object-src 'none'"
|
||||
].join('; ');
|
||||
|
||||
const nginx = `# Généré par scripts/postbuild-csp.mjs — ne pas éditer à la main.
|
||||
# À inclure dans la config nginx de l'app YunoHost, dans le bloc location
|
||||
# servant les fichiers statiques, ex. :
|
||||
# location / {
|
||||
# alias /var/www/<app>/;
|
||||
# include /var/www/<app>/nginx-csp.conf;
|
||||
# try_files $uri $uri/ =404;
|
||||
# }
|
||||
# Note nginx : un add_header au niveau le plus précis REMPLACE les add_header
|
||||
# hérités des niveaux supérieurs — une éventuelle CSP globale est neutralisée.
|
||||
# Régénéré à chaque build : le ré-uploader avec le reste du contenu de build/.
|
||||
add_header Content-Security-Policy "${csp}" always;
|
||||
`;
|
||||
|
||||
const htaccess = `# Généré par scripts/postbuild-csp.mjs — ne pas éditer à la main.
|
||||
# Équivalent Apache du snippet nginx (secours uniquement — nginx l'ignore).
|
||||
<IfModule mod_headers.c>
|
||||
Header unset Content-Security-Policy
|
||||
Header always unset Content-Security-Policy
|
||||
Header set Content-Security-Policy "${csp}"
|
||||
</IfModule>
|
||||
`;
|
||||
|
||||
writeFileSync(join(buildDir, 'nginx-csp.conf'), nginx);
|
||||
writeFileSync(join(buildDir, '.htaccess'), htaccess);
|
||||
console.log(`postbuild-csp: nginx-csp.conf + .htaccess écrits (${hashes.size} empreinte(s))`);
|
||||
Reference in New Issue
Block a user