Grafana tourne en SQLite sur emptyDir → migration complète du schéma à chaque
démarrage de pod, > 160 s sur Raspberry Pi. La liveson (initialDelay 60 + 10×10s)
tuait Grafana en pleine migration → CrashLoop du nouveau pod à chaque rollout
(révélé par le rollout du dashboard prospection). Ajoute une startupProbe (~10 min)
et relève failureThreshold de la liveness (filet si le chart n'expose pas startupProbe).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Consomme les métriques poussées par le pipeline prospection au Pushgateway
(job=prospection, déjà scrapé). Additif — n'affecte aucun dashboard existant.
- grafana : provider + dashboard « Prospection — pipeline BI missions » inliné
(grafana.dashboards.prospection, json) — vue d'ensemble (fraîcheur/statut/durée/
erreurs/missions/score), collecte par étape (table + historique), modèle de
données (opportunités A/B, offres/entités), livraison (brief/Telegram) + panneau
Alertes actives. Inline plutôt que ConfigMap externe : grafana est déployé via un
HelmChart CRD (tool lib), l'inline évite toute hypothèse de namespace.
- prometheus : groupe d'alertes `prospection` (serverFiles.alerting_rules.yml) —
RunStale (>25h), RunFailed, StepError, NoOffers, BriefNotSent.
NB : la livraison des alertes (Alertmanager → Telegram) n'est pas câblée dans le
cluster (server.alertmanagers vide, aucun receiver) ; les règles restent visibles
dans Prometheus /alerts + le dashboard. Câblage delivery = décision séparée.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Ajoute prospection à la liste app_policies → crée le rôle JWT gitea_cicd_prospection
et la policy prospection-ops, requis par le workflow vault.yaml du repo prospection
(qui provisionne le rôle k8s-auth + la policy de lecture kvv2/prospection/*).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds an optional kv_read_paths list to the app_policy module (default []) so an
app's env=prod runtime policy can read extra kvv2 data paths — e.g. a shared
backup-creds path owned by another app. Plumbed through the root applications
schema + module call (dynamic rule, read+list).
Set for erp: kv_read_paths = ["kvv2/data/longhorn/gcs-backup"], so the dedicated
Dolibarr backup CronJob (erp chart, gated) can read the existing GCS HMAC creds
via its own VaultStaticSecret instead of borrowing the Longhorn secret
cross-namespace or duplicating credentials.
No-op for every other app (default []). Only the `erp` runtime policy gains one
read+list rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The `applications` object field was declared `policies` in variables.tf, but
the cms tfvars entry, the runbook (doc/runbooks/new-web-app/03-vault-platform.md),
the guidebook (vibe/guidebooks/tools/secrets-and-vso.md) and the module input
(modules/app_policy variable `ops_policies`) all use the name `ops_policies`.
Because Terraform silently drops unknown attributes when converting a value to
an object() type, cms's `ops_policies = ["factory__cf_r2_arcodange_tf"]` was
discarded and `each.value.policies` fell back to [] — so gitea_cicd_cms never
received the `factory__cf_r2_arcodange_tf` token policy (read on
kvv1/cloudflare/r2/arcodange-tf + kvv1/zoho/self_client, defined in
factory iac/cloudflare.tf). cms CI was missing its Cloudflare R2 Terraform-state
permissions.
Fix at the root: rename the schema field `policies` -> `ops_policies` (and its
single reference main.tf:82 `each.value.policies` -> `each.value.ops_policies`),
aligning the whole chain. This is lower-churn than renaming the tfvars key (the
chosen alternative would also have required fixing the runbook + guidebook, which
both already document `ops_policies`) and prevents the next app created from the
runbook from re-introducing the same silently-dropped key.
Behavioural change: gitea_cicd_cms gains `factory__cf_r2_arcodange_tf` in its
token_policies. No other app sets this field (all default []), so no other role
changes. Reviewer: confirm the R2 policy is the intended grant for cms CI.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ADR-0002 Phase D, Vault layer. `erp` gains `envs = ["prod", "sandbox"]`,
which flows into the app_policy module (main.tf:81 `envs = each.value.envs`).
For erp the module now resolves instances = ["erp", "erp-sandbox"], so the
apply:
- ADDS vault_policy.app_non_prod["erp-sandbox"] — the runtime policy
named `erp-sandbox` (read kvv2/data/erp-sandbox/* +
postgres/creds/erp-sandbox*), consumed by the sandbox pod's VSO.
- UPDATES vault_policy.ops["erp"] in place — the `erp-ops` CI policy
gains the erp-sandbox kvv2 data/delete/undelete/destroy/metadata
rules + the erp-sandbox values in the k8s-role allowed_parameter
lists, so CI can manage the sandbox instance. The glob rules
(postgres/roles/erp*, kvv1/cloudflare/erp*, auth/kubernetes/role/erp*)
already covered erp-sandbox, so they don't change.
No destroy/replace. prod `erp` runtime policy + every other app render
byte-identical (their envs still default to ["prod"]).
Diff kept to the single erp line — the pre-existing cms/crowdsec/plausible
alignment is left as-is on main (not reformatting unrelated entries).
D2 of Phase D. D1 (postgres DB+role) = factory#17 (merged). D3 (erp iac
creds + KV) and D4 (ArgoCD) follow.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Phase A of the multi-environment evolution agreed in the erp repo design
thread. Both modules gain an optional env coordinate that defaults to
"prod"; by the elision rule, env=prod produces the existing single-env
derived names character-for-character, so every existing app's tofu plan
is a no-op.
app_roles (per-instance module — caller iterates over envs):
- variables.tf: add optional env = "prod"
- main.tf: compute local.instance via elision rule + local.owner_role
(snake-case <name>_<env>_role for the Postgres owner). The name/env/
database locals are grouped so fmt keeps the existing `name` alignment
(no whitespace churn on unchanged keys).
- main.tf: substitute local.name -> local.instance / local.owner_role in
the dynamic role name, k8s role name, SA bindings, token_policies
- outputs.tf: add env + instance outputs; kvv2_path_prefix derives from
local.instance (== local.name when env=prod → backwards-compat)
app_policy (per-repo module — accepts list of envs):
- variables.tf: add optional envs = ["prod"]
- main.tf: compute local.instances + local.non_prod_instances; remove the
now-dead bound_service_account_* alias locals (the allowed_parameter
blocks build their values from per_instance_sa_* maps instead)
- main.tf: kvv2 ops rules become dynamic blocks iterating local.instances
in the original order (data, delete, undelete, destroy, metadata), so a
prod-only app renders a byte-identical policy document
- main.tf: allowed_parameter for bound_service_account_* + token_policies
use comprehensions over local.instances (1-element → identical to old
static values for prod-only apps)
- main.tf: keep vault_policy.app (env=prod runtime policy) at its original
address; add vault_policy.app_non_prod via for_each over non_prod_instances
(empty set for prod-only apps → no new resources)
Top-level wiring:
- iac/variables.tf: add envs = optional(list(string), ["prod"]) to the
applications set(object) type
- iac/main.tf: pass envs = each.value.envs to app_policies
Verified: `tofu fmt -check` clean on all touched files, `tofu validate`
passes. Backwards-compat reasoning for the no-op plan is in the PR body.
Phase B (factory postgres iac + argocd + runbook docs) and Phase D
(erp iac/main.tf for_each + activate sandbox) follow in their own PRs.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
RollingUpdate with maxSurge>0 creates a new pod before terminating the old one,
causing a Multi-Attach error on the RWO PVCs (crowdsec-db-pvc, crowdsec-config-pvc).
Recreate terminates the old pod first, then starts the new one.