1

Extraction des secrets

This commit is contained in:
2025-11-28 13:40:20 +01:00
parent fd27dc7fb6
commit 8908c7f6cf
11 changed files with 148 additions and 26 deletions

3
.gitignore vendored
View File

@@ -9,4 +9,5 @@ data/stats.json
content/stats/data/stats.json content/stats/data/stats.json
content/stats/images content/stats/images
.venv .venv
__pycache__ __pycache__
.env

View File

@@ -3,9 +3,16 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/.env" ]; then
# Export all variables from .env for the duration of the script
set -a
. "$SCRIPT_DIR/.env"
set +a
fi
# Configuration # Configuration
DEST_USER="root" DEST_USER="${DEPLOY_DEST_USER:?DEPLOY_DEST_USER manquant}"
DEST_HOST="server-main.home.arpa" DEST_HOST="${DEPLOY_DEST_HOST:?DEPLOY_DEST_HOST manquant}"
DEST_DIR="/var/lib/www/richard-dern.fr/" DEST_DIR="/var/lib/www/richard-dern.fr/"
HUGO_ENV="production" HUGO_ENV="production"
TARGET_OWNER="caddy:caddy" TARGET_OWNER="caddy:caddy"

View File

@@ -21,18 +21,13 @@ const fsSync = require('fs');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');
const readline = require('readline'); const readline = require('readline');
const { loadToolsConfig } = require('./lib/config');
const ROOT = process.cwd(); const ROOT = process.cwd();
const LEGO_ROOT = path.join(ROOT, 'content', 'collections', 'lego'); const LEGO_ROOT = path.join(ROOT, 'content', 'collections', 'lego');
const CONFIG_PATH = path.join(ROOT, 'tools', 'config.json');
const CACHE_ROOT = path.join(ROOT, 'tools', 'cache', 'rebrickable'); const CACHE_ROOT = path.join(ROOT, 'tools', 'cache', 'rebrickable');
const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000; const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
function loadConfig() {
const raw = fsSync.readFileSync(CONFIG_PATH, 'utf8');
return JSON.parse(raw);
}
function slugify(input) { function slugify(input) {
return input return input
.normalize('NFD') .normalize('NFD')
@@ -222,7 +217,7 @@ async function main() {
process.exit(1); process.exit(1);
} }
const config = loadConfig(); const config = await loadToolsConfig();
const apiKey = config.rebrickable.apiKey; const apiKey = config.rebrickable.apiKey;
const host = 'rebrickable.com'; const host = 'rebrickable.com';

View File

@@ -1,6 +1,6 @@
{ {
"rebrickable": { "rebrickable": {
"apiKey": "" "apiKey": null
}, },
"externalLinks": { "externalLinks": {
"cacheDir": "tools/cache", "cacheDir": "tools/cache",
@@ -24,10 +24,10 @@
"precipitationThreshold": 0.1, "precipitationThreshold": 0.1,
"providers": { "providers": {
"influxdb": { "influxdb": {
"url": "", "url": null,
"org": "Dern", "org": "Dern",
"bucket": "weather", "bucket": "weather",
"token": "==", "token": null,
"windowMinutes": 60, "windowMinutes": 60,
"precipitationThreshold": 0.1, "precipitationThreshold": 0.1,
"sensors": { "sensors": {
@@ -85,17 +85,17 @@
} }
}, },
"openMeteo": { "openMeteo": {
"latitude": , "latitude": null,
"longitude": , "longitude": null,
"timezone": "Europe/Paris", "timezone": "Europe/Paris",
"pressureOffset": 40, "pressureOffset": 40,
"illuminanceToLuxFactor": 126.7, "illuminanceToLuxFactor": 126.7,
"windowMinutes": 90, "windowMinutes": 90,
"precipitationThreshold": 0.1 "precipitationThreshold": 0.1
}
} }
}, },
"goaccess": { "goaccess": {
"url": "" "url": null
} }
} }
}

View File

@@ -2,6 +2,9 @@
const fs = require("fs/promises"); const fs = require("fs/promises");
const path = require("path"); const path = require("path");
const { loadEnv } = require("./lib/env");
loadEnv();
const DEFAULT_CONFIG_PATH = "tools/stats.json"; const DEFAULT_CONFIG_PATH = "tools/stats.json";
const DEFAULT_DATA_OUTPUT = "content/stats/data/stats.json"; const DEFAULT_DATA_OUTPUT = "content/stats/data/stats.json";

View File

@@ -1,20 +1,68 @@
const fs = require("fs/promises"); const fs = require("fs/promises");
const path = require("path"); const path = require("path");
const { loadEnv } = require("./env");
let cached = null; let cached = null;
function parseNumber(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
function applyEnvOverrides(config = {}) {
const merged = { ...config };
merged.rebrickable = { ...(config.rebrickable || {}) };
if (process.env.REBRICKABLE_API_KEY) {
merged.rebrickable.apiKey = process.env.REBRICKABLE_API_KEY;
}
const weather = config.weather || {};
const providers = weather.providers || {};
merged.weather = { ...weather, providers: { ...providers } };
merged.weather.providers.influxdb = { ...(providers.influxdb || {}) };
merged.weather.providers.openMeteo = { ...(providers.openMeteo || {}) };
if (process.env.WEATHER_INFLUXDB_URL) {
merged.weather.providers.influxdb.url = process.env.WEATHER_INFLUXDB_URL;
}
if (process.env.WEATHER_INFLUXDB_TOKEN) {
merged.weather.providers.influxdb.token = process.env.WEATHER_INFLUXDB_TOKEN;
}
const envLatitude = parseNumber(process.env.WEATHER_OPEN_METEO_LATITUDE);
if (envLatitude !== null) {
merged.weather.providers.openMeteo.latitude = envLatitude;
}
const envLongitude = parseNumber(process.env.WEATHER_OPEN_METEO_LONGITUDE);
if (envLongitude !== null) {
merged.weather.providers.openMeteo.longitude = envLongitude;
}
merged.goaccess = { ...(config.goaccess || {}) };
if (process.env.GOACCESS_URL) {
merged.goaccess.url = process.env.GOACCESS_URL;
}
return merged;
}
async function loadToolsConfig(configPath = "tools/config.json") { async function loadToolsConfig(configPath = "tools/config.json") {
const resolved = path.resolve(configPath); const resolved = path.resolve(configPath);
if (cached && cached.path === resolved) { if (cached && cached.path === resolved) {
return cached.data; return cached.data;
} }
loadEnv();
const raw = await fs.readFile(resolved, "utf8"); const raw = await fs.readFile(resolved, "utf8");
const data = JSON.parse(raw); const data = applyEnvOverrides(JSON.parse(raw));
cached = { path: resolved, data }; cached = { path: resolved, data };
return data; return data;
} }
module.exports = { module.exports = {
applyEnvOverrides,
loadToolsConfig, loadToolsConfig,
}; };

33
tools/lib/env.js Normal file
View File

@@ -0,0 +1,33 @@
const fs = require("fs");
const path = require("path");
let envLoaded = false;
function loadEnv(envPath = path.resolve(__dirname, "..", "..", ".env")) {
if (envLoaded) return;
envLoaded = true;
if (!fs.existsSync(envPath)) return;
const content = fs.readFileSync(envPath, "utf8");
const lines = content.split(/\r?\n/);
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const separator = trimmed.indexOf("=");
if (separator === -1) continue;
const key = trimmed.slice(0, separator).trim();
const value = trimmed.slice(separator + 1);
if (!key || process.env[key] !== undefined) continue;
process.env[key] = value;
}
}
module.exports = {
loadEnv,
};

View File

@@ -1,5 +1,7 @@
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const { applyEnvOverrides } = require("../config");
const { loadEnv } = require("../env");
const DEFAULT_WEATHER_CONFIG = { const DEFAULT_WEATHER_CONFIG = {
timezone: "Europe/Paris", timezone: "Europe/Paris",
@@ -20,6 +22,8 @@ const DEFAULT_WEATHER_CONFIG = {
}; };
function loadWeatherConfig(configPath = path.resolve(__dirname, "..", "..", "config.json")) { function loadWeatherConfig(configPath = path.resolve(__dirname, "..", "..", "config.json")) {
loadEnv();
let raw = {}; let raw = {};
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
@@ -30,7 +34,8 @@ function loadWeatherConfig(configPath = path.resolve(__dirname, "..", "..", "con
} }
} }
const weather = raw.weather || {}; const withEnv = applyEnvOverrides(raw);
const weather = withEnv.weather || {};
const providers = { const providers = {
...DEFAULT_WEATHER_CONFIG.providers, ...DEFAULT_WEATHER_CONFIG.providers,

View File

@@ -19,7 +19,10 @@ function latestMonthEntry(months) {
async function run({ stat }) { async function run({ stat }) {
const toolsConfig = await loadToolsConfig(); const toolsConfig = await loadToolsConfig();
const url = stat.url || toolsConfig.goaccess?.url || ""; const url = stat.url || toolsConfig.goaccess?.url;
if (!url) {
throw new Error("URL GoAccess manquante (GOACCESS_URL ou goaccess.url dans tools/config.json)");
}
const metric = stat.metric || "hits"; const metric = stat.metric || "hits";
const windowDays = Number.isFinite(stat.days) ? stat.days : 30; const windowDays = Number.isFinite(stat.days) ? stat.days : 30;
const data = await loadData(url); const data = await loadData(url);

View File

@@ -14,6 +14,25 @@ if TOOLS_DIR not in sys.path:
sys.path.append(TOOLS_DIR) sys.path.append(TOOLS_DIR)
def load_env(env_path=None):
path = env_path or os.path.join(ROOT_DIR, ".env")
if not os.path.exists(path):
return
try:
with open(path, "r", encoding="utf-8") as handle:
for line in handle:
stripped = line.strip()
if not stripped or stripped.startswith("#") or "=" not in stripped:
continue
key, value = stripped.split("=", 1)
key = key.strip()
if not key or key in os.environ:
continue
os.environ[key] = value
except Exception as exc: # noqa: BLE001
print(f"Failed to load .env: {exc}", file=sys.stderr)
def load_config(): def load_config():
cfg_path = os.path.join(ROOT_DIR, "tools", "config.json") cfg_path = os.path.join(ROOT_DIR, "tools", "config.json")
try: try:
@@ -63,8 +82,13 @@ def main():
public_path = payload.get("publicPath") public_path = payload.get("publicPath")
url = payload.get("stat", {}).get("url") url = payload.get("stat", {}).get("url")
load_env()
cfg = load_config() cfg = load_config()
goaccess_url = url or (cfg.get("goaccess") or {}).get("url") or "" goaccess_url = url or os.environ.get("GOACCESS_URL") or (cfg.get("goaccess") or {}).get("url")
if not goaccess_url:
print("Missing GoAccess URL (set GOACCESS_URL or goaccess.url in tools/config.json)", file=sys.stderr)
sys.exit(1)
try: try:
data = fetch_goaccess(goaccess_url) data = fetch_goaccess(goaccess_url)

View File

@@ -11,7 +11,10 @@ async function run({ stat, outputPath, publicPath }) {
} }
const toolsConfig = await loadToolsConfig(); const toolsConfig = await loadToolsConfig();
const url = stat.url || toolsConfig.goaccess?.url || ""; const url = stat.url || toolsConfig.goaccess?.url;
if (!url) {
throw new Error("URL GoAccess manquante (GOACCESS_URL ou goaccess.url dans tools/config.json)");
}
const data = await fetchGoAccessJson(url); const data = await fetchGoAccessJson(url);
const months = groupVisitsByMonth(data, { adjustCrawlers: true }); const months = groupVisitsByMonth(data, { adjustCrawlers: true });