Extraction des secrets
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ content/stats/data/stats.json
|
||||
content/stats/images
|
||||
.venv
|
||||
__pycache__
|
||||
.env
|
||||
|
||||
11
deploy.sh
11
deploy.sh
@@ -3,9 +3,16 @@ set -euo pipefail
|
||||
|
||||
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
|
||||
DEST_USER="root"
|
||||
DEST_HOST="server-main.home.arpa"
|
||||
DEST_USER="${DEPLOY_DEST_USER:?DEPLOY_DEST_USER manquant}"
|
||||
DEST_HOST="${DEPLOY_DEST_HOST:?DEPLOY_DEST_HOST manquant}"
|
||||
DEST_DIR="/var/lib/www/richard-dern.fr/"
|
||||
HUGO_ENV="production"
|
||||
TARGET_OWNER="caddy:caddy"
|
||||
|
||||
@@ -21,18 +21,13 @@ const fsSync = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const readline = require('readline');
|
||||
const { loadToolsConfig } = require('./lib/config');
|
||||
|
||||
const ROOT = process.cwd();
|
||||
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 ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
function loadConfig() {
|
||||
const raw = fsSync.readFileSync(CONFIG_PATH, 'utf8');
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
function slugify(input) {
|
||||
return input
|
||||
.normalize('NFD')
|
||||
@@ -222,7 +217,7 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
const config = await loadToolsConfig();
|
||||
const apiKey = config.rebrickable.apiKey;
|
||||
const host = 'rebrickable.com';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"rebrickable": {
|
||||
"apiKey": ""
|
||||
"apiKey": null
|
||||
},
|
||||
"externalLinks": {
|
||||
"cacheDir": "tools/cache",
|
||||
@@ -24,10 +24,10 @@
|
||||
"precipitationThreshold": 0.1,
|
||||
"providers": {
|
||||
"influxdb": {
|
||||
"url": "",
|
||||
"url": null,
|
||||
"org": "Dern",
|
||||
"bucket": "weather",
|
||||
"token": "==",
|
||||
"token": null,
|
||||
"windowMinutes": 60,
|
||||
"precipitationThreshold": 0.1,
|
||||
"sensors": {
|
||||
@@ -85,17 +85,17 @@
|
||||
}
|
||||
},
|
||||
"openMeteo": {
|
||||
"latitude": ,
|
||||
"longitude": ,
|
||||
"latitude": null,
|
||||
"longitude": null,
|
||||
"timezone": "Europe/Paris",
|
||||
"pressureOffset": 40,
|
||||
"illuminanceToLuxFactor": 126.7,
|
||||
"windowMinutes": 90,
|
||||
"precipitationThreshold": 0.1
|
||||
"pressureOffset": 40,
|
||||
"illuminanceToLuxFactor": 126.7,
|
||||
"windowMinutes": 90,
|
||||
"precipitationThreshold": 0.1
|
||||
}
|
||||
}
|
||||
},
|
||||
"goaccess": {
|
||||
"url": ""
|
||||
"url": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const { loadEnv } = require("./lib/env");
|
||||
|
||||
loadEnv();
|
||||
|
||||
const DEFAULT_CONFIG_PATH = "tools/stats.json";
|
||||
const DEFAULT_DATA_OUTPUT = "content/stats/data/stats.json";
|
||||
|
||||
@@ -1,20 +1,68 @@
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const { loadEnv } = require("./env");
|
||||
|
||||
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") {
|
||||
const resolved = path.resolve(configPath);
|
||||
if (cached && cached.path === resolved) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
loadEnv();
|
||||
const raw = await fs.readFile(resolved, "utf8");
|
||||
const data = JSON.parse(raw);
|
||||
const data = applyEnvOverrides(JSON.parse(raw));
|
||||
cached = { path: resolved, data };
|
||||
return data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
applyEnvOverrides,
|
||||
loadToolsConfig,
|
||||
};
|
||||
|
||||
33
tools/lib/env.js
Normal file
33
tools/lib/env.js
Normal 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,
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { applyEnvOverrides } = require("../config");
|
||||
const { loadEnv } = require("../env");
|
||||
|
||||
const DEFAULT_WEATHER_CONFIG = {
|
||||
timezone: "Europe/Paris",
|
||||
@@ -20,6 +22,8 @@ const DEFAULT_WEATHER_CONFIG = {
|
||||
};
|
||||
|
||||
function loadWeatherConfig(configPath = path.resolve(__dirname, "..", "..", "config.json")) {
|
||||
loadEnv();
|
||||
|
||||
let raw = {};
|
||||
|
||||
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 = {
|
||||
...DEFAULT_WEATHER_CONFIG.providers,
|
||||
|
||||
@@ -19,7 +19,10 @@ function latestMonthEntry(months) {
|
||||
|
||||
async function run({ stat }) {
|
||||
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 windowDays = Number.isFinite(stat.days) ? stat.days : 30;
|
||||
const data = await loadData(url);
|
||||
|
||||
@@ -14,6 +14,25 @@ if TOOLS_DIR not in sys.path:
|
||||
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():
|
||||
cfg_path = os.path.join(ROOT_DIR, "tools", "config.json")
|
||||
try:
|
||||
@@ -63,8 +82,13 @@ def main():
|
||||
public_path = payload.get("publicPath")
|
||||
url = payload.get("stat", {}).get("url")
|
||||
|
||||
load_env()
|
||||
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:
|
||||
data = fetch_goaccess(goaccess_url)
|
||||
|
||||
@@ -11,7 +11,10 @@ async function run({ stat, outputPath, publicPath }) {
|
||||
}
|
||||
|
||||
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 months = groupVisitsByMonth(data, { adjustCrawlers: true });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user