railiance-platform/Makefile
codex cb321deeab
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Finish RPF-WP-0019 apps-pg controls and recovery
2026-08-20 22:58:45 +02:00

467 lines
25 KiB
Makefile

SHELL := /usr/bin/env bash
.DEFAULT_GOAL := help
KUBECONFIG ?= $(firstword $(wildcard $(HOME)/.kube/config-railiance01) $(wildcard $(HOME)/.kube/config))
KUBECTL_BIN ?= $(firstword $(shell command -v kubectl 2>/dev/null) $(wildcard $(HOME)/.local/bin/kubectl) kubectl)
KUBECTL := $(KUBECTL_BIN) --kubeconfig=$(KUBECONFIG)
# Cluster identity pin (RPF-WP-0019).
#
# Two reachable clusters each carry a CNPG Cluster named `apps-pg` in a
# namespace named `databases`. Selecting by kubeconfig FILENAME does not
# protect against applying to the wrong one: KUBECONFIG is an environment
# variable, `?=` does not override it, and both files resolve to a
# 127.0.0.1 tunnel port. So the guard pins cluster IDENTITY instead —
# the kube-system namespace UID, which is unique per cluster and stable
# for its lifetime.
#
# railiance01 (k3s v1.35.1) carries platform-pg, forgejo-db and both
# apps-pg consumers. The other cluster carries gitea-db and only one
# apps-pg consumer; `db-status` legitimately targets it, which is why the
# guard is applied per-target rather than globally.
RAILIANCE01_CLUSTER_UID ?= a553c742-0115-43d4-99a4-a5ca56fe0786
HELM := helm --kubeconfig=$(KUBECONFIG)
NAMESPACE := platform
PG_CHART_VERSION ?= 16.2.2
VALKEY_CHART_VERSION ?= 2.x
OPENBAO_CHART_VERSION ?= 0.28.2
OPENBAO_NAMESPACE ?= openbao
OPENBAO_RELEASE ?= openbao
OPENBAO_VALUES ?= helm/openbao-values.yaml
OPENBAO_MIDDLEWARE ?= helm/openbao-middleware.yaml
OPENBAO_UI_OVERLAY_DIR ?= helm/openbao-ui-overlay
OPENBAO_UI_OVERLAY_K8S ?= helm/openbao-ui-overlay-k8s.yaml
OPENBAO_VERIFY_AUTH_ARGS ?=
OPENBAO_RESTORE_EVIDENCE ?= /tmp/netkingdom-openbao-restore-drill/evidence.json
OPENBAO_EMERGENCY_EVIDENCE ?= /tmp/netkingdom-openbao-emergency-drill/evidence.json
EXTERNAL_SECRETS_NAMESPACE ?= external-secrets
ARGOCD_NAMESPACE ?= argocd
ARGOCD_BOOTSTRAP_DIR ?= argocd/bootstrap
ARGOCD_REPOSITORY_SECRET ?=
CREDENTIAL_GRANTS ?= credential-grants/catalog.yaml
CREDENTIAL_CHANGE ?= CCR-2026-0001
CREDENTIAL_CHANGE_EVIDENCE_ARGS ?=
CREDENTIAL_CHANGE_LIFECYCLE_ACTION ?= deactivate
CREDENTIAL_CHANGE_LIFECYCLE_ARGS ?=
CREDENTIAL_CHANGE_IMPORT_ARGS ?=
STATE_HUB_URL ?= http://127.0.0.1:8000
OPENBAO_TOKEN_GRANT_ARGS ?=
OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS ?=
OPENBAO_WORKLOAD_KV_ARGS ?=
CREDENTIAL_HELPER_GLOBAL_ARGS ?=
CREDENTIAL_HELPER_ARGS ?=
CREDENTIAL_HELPER_CHILD_ENV ?=
CREDENTIAL_HELPER_PURPOSE ?= flex-auth-openbao-smoke
##@ CloudNative PG (cnpg) — primary database operator
db-deploy: ## Apply Gitea cnpg Cluster (creates gitea-db in databases namespace)
$(KUBECTL) apply -f helm/gitea-db-cluster.yaml
db-status: ## Show cnpg cluster health
$(KUBECTL) cnpg status gitea-db -n databases 2>/dev/null || \
$(KUBECTL) get cluster gitea-db -n databases -o wide
db-shell: ## Open psql shell on gitea-db primary
$(KUBECTL) cnpg psql gitea-db -n databases -- -U gitea gitea
db-logs: ## Tail gitea-db primary logs
$(KUBECTL) logs -n databases -l cnpg.io/cluster=gitea-db -f --tail=50
##@ Forgejo database (railiance01 production forge)
forgejo-db-deploy: ## Apply forgejo-db cnpg Cluster + NetworkPolicies on railiance01
$(KUBECTL) apply -f helm/forgejo-db-cluster.yaml
$(KUBECTL) apply -f helm/forgejo-db-networkpolicies.yaml
forgejo-db-status: ## Show forgejo-db cnpg cluster health
$(KUBECTL) cnpg status forgejo-db -n databases 2>/dev/null || \
$(KUBECTL) get cluster forgejo-db -n databases -o wide
forgejo-db-shell: ## Open psql shell on forgejo-db primary
$(KUBECTL) cnpg psql forgejo-db -n databases -- -U forgejo forgejo
CONSUMING_ENTITY ?=
ESTIMATE_EUR ?=
CONSUMPTION_CLASS ?= new-order
CONSUMPTION_MODE_FILE ?= data/consumption-mode/current.json
##@ Consumption mode (RAILIANCE-WP-0017)
consumption-preflight: ## Refuse a new order that exceeds a restricted entity's allowance
@test -n "$(CONSUMING_ENTITY)" || { echo 'set CONSUMING_ENTITY=entity:<slug>' >&2; exit 2; }
CONSUMPTION_MODE_FILE='$(CONSUMPTION_MODE_FILE)' python3 scripts/consumption_mode.py check \
--entity '$(CONSUMING_ENTITY)' \
--class '$(CONSUMPTION_CLASS)' \
$(if $(ESTIMATE_EUR),--estimate-eur '$(ESTIMATE_EUR)')
##@ Shared apps-pg (S5 application databases)
cluster-id: ## Print the kube-system UID of the cluster KUBECONFIG currently selects
@echo "kubeconfig: $(KUBECONFIG)"
@echo "cluster-uid: $$($(KUBECTL) get ns kube-system -o jsonpath='{.metadata.uid}' 2>/dev/null || echo UNREACHABLE)"
railiance01-guard: ## Fail closed unless KUBECONFIG selects railiance01
@uid=$$($(KUBECTL) get ns kube-system -o jsonpath='{.metadata.uid}' 2>/dev/null); \
if [ -z "$$uid" ]; then \
echo "railiance01-guard: cluster unreachable via $(KUBECONFIG)" >&2; \
echo " hint: unset KUBECONFIG, or set it to ~/.kube/config-railiance01" >&2; \
exit 2; \
fi; \
if [ "$$uid" != "$(RAILIANCE01_CLUSTER_UID)" ]; then \
echo "railiance01-guard: REFUSING - wrong cluster" >&2; \
echo " kubeconfig: $(KUBECONFIG)" >&2; \
echo " expected: $(RAILIANCE01_CLUSTER_UID)" >&2; \
echo " found: $$uid" >&2; \
echo " both clusters carry apps-pg in namespace databases; applying here would hit the wrong one." >&2; \
exit 2; \
fi; \
echo "railiance01-guard: ok ($$uid)"
apps-pg-verify-capacity: ## Verify cell ceiling, role limits and distinct backup prefixes
python3 tools/verify_apps_pg_capacity.py helm/apps-pg-cluster.yaml helm/apps-pg-2-cluster.yaml
apps-pg-deploy: railiance01-guard apps-pg-verify-capacity ## Apply shared apps-pg cnpg Cluster + NetworkPolicies
@if [ -n "$(CONSUMING_ENTITY)" ]; then \
$(MAKE) consumption-preflight CONSUMING_ENTITY='$(CONSUMING_ENTITY)' \
ESTIMATE_EUR='$(ESTIMATE_EUR)' CONSUMPTION_CLASS=new-order; \
fi
$(KUBECTL) apply -f helm/apps-pg-cluster.yaml
$(KUBECTL) apply -f helm/apps-pg-networkpolicies.yaml
apps-pg-backup-deploy: railiance01-guard ## Apply the daily backup after the governed S3 Secret exists
@$(KUBECTL) get secret platform-pg-backup-s3 -n databases >/dev/null || { echo 'missing governed platform-pg-backup-s3 secret' >&2; exit 2; }
$(KUBECTL) apply -f helm/apps-pg-backup.yaml
apps-pg-isolation-probe: railiance01-guard ## Run bounded live consumer-boundary and connection-saturation probes
KUBECONFIG='$(KUBECONFIG)' KUBECTL_BIN='$(KUBECTL_BIN)' tools/apps-pg-isolation-probe.sh
apps-pg-overflow-dry-run: railiance01-guard apps-pg-verify-capacity ## Server-validate the unapplied overflow cell
@$(KUBECTL) get secret apps-pg-2-credentials -n databases >/dev/null || { echo 'missing distinct apps-pg-2-credentials secret' >&2; exit 2; }
$(KUBECTL) apply --dry-run=server -f helm/apps-pg-2-cluster.yaml
$(KUBECTL) apply --dry-run=server -f helm/apps-pg-2-networkpolicies.yaml
$(KUBECTL) apply --dry-run=server -f helm/apps-pg-2-backup.yaml
apps-pg-overflow-deploy: apps-pg-overflow-dry-run ## Provision only for an approved fourth consumer
$(KUBECTL) apply -f helm/apps-pg-2-cluster.yaml
$(KUBECTL) apply -f helm/apps-pg-2-networkpolicies.yaml
$(KUBECTL) apply -f helm/apps-pg-2-backup.yaml
apps-pg-status: railiance01-guard ## Show apps-pg cnpg cluster health
$(KUBECTL) cnpg status apps-pg -n databases 2>/dev/null || \
$(KUBECTL) get cluster apps-pg -n databases -o wide
apps-pg-shell: railiance01-guard ## Open psql shell on apps-pg primary as apps_admin / apps_meta
$(KUBECTL) cnpg psql apps-pg -n databases -- -U apps_admin apps_meta 2>/dev/null || \
$(KUBECTL) exec -it -n databases apps-pg-1 -- psql -U apps_admin apps_meta
apps-pg-logs: ## Tail apps-pg primary logs
$(KUBECTL) logs -n databases -l cnpg.io/cluster=apps-pg -f --tail=50
net-kingdom-pg-inter-hub-networkpolicy-deploy: ## Allow inter-hub to reach net-kingdom-pg
$(KUBECTL) apply -f helm/net-kingdom-pg-inter-hub-networkpolicy.yaml
##@ PostgreSQL HA (retired — CNPG superseded this)
pg-deploy: ## Retired: refuse to deploy Bitnami postgresql-ha
@echo 'pg-deploy is retired (RAILIANCE-WP-0016 item 14). Use apps-pg / forgejo-db / rapp-postgres.' >&2; exit 2
pg-status: ## Confirm Bitnami postgresql-ha is absent
@if $(KUBECTL) get ns $(NAMESPACE) >/dev/null 2>&1; then \
$(KUBECTL) get pods -n $(NAMESPACE) -l app.kubernetes.io/name=postgresql-ha; \
else \
echo 'postgresql-ha retired: namespace $(NAMESPACE) is absent'; \
fi
pg-pgpool-check: ## Retired: no pgpool secret to check
@echo 'pg-pgpool-check is retired with postgresql-ha (RAILIANCE-WP-0016 item 14).' >&2; exit 2
##@ Valkey (cache) — declared, not deployed
valkey-deploy: ## Gated: do not deploy Valkey until a consumer rapp exists
@echo 'valkey-deploy is gated (RAILIANCE-WP-0016 item 14): no live consumer, nothing to extract from S2.' >&2; exit 2
valkey-status: ## Confirm Valkey is absent
@if $(KUBECTL) get ns $(NAMESPACE) >/dev/null 2>&1; then \
$(KUBECTL) get pods -n $(NAMESPACE) -l app.kubernetes.io/name=valkey; \
else \
echo 'valkey undeployed: namespace $(NAMESPACE) is absent'; \
fi
##@ OpenBao (secrets)
openbao-repo: ## Add / update the official OpenBao Helm repository
$(HELM) repo add openbao https://openbao.github.io/openbao-helm --force-update
$(HELM) repo update openbao
openbao-dry-run: openbao-repo ## Render the OpenBao Helm release without applying it
$(HELM) upgrade --install $(OPENBAO_RELEASE) openbao/openbao \
--version $(OPENBAO_CHART_VERSION) \
--namespace $(OPENBAO_NAMESPACE) \
--create-namespace \
-f $(OPENBAO_VALUES) \
--dry-run
openbao-overlay-apply: ## Apply KeyCape login overlay gateway and assets
OPENBAO_UI_OVERLAY_DIR=$(OPENBAO_UI_OVERLAY_DIR) \
OPENBAO_UI_OVERLAY_K8S=$(OPENBAO_UI_OVERLAY_K8S) \
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
scripts/openbao-ui-overlay-apply.sh
openbao-verify-login-overlay: ## Verify public KeyCape login overlay is active
OPENBAO_UI_OVERLAY_DIR=$(OPENBAO_UI_OVERLAY_DIR) \
scripts/openbao-verify-login-overlay.sh $(OPENBAO_VERIFY_LOGIN_OVERLAY_ARGS)
openbao-deploy: openbao-repo ## Deploy / upgrade OpenBao to the openbao namespace
$(KUBECTL) create namespace $(OPENBAO_NAMESPACE) --dry-run=client -o yaml | $(KUBECTL) apply -f -
$(KUBECTL) apply -f $(OPENBAO_MIDDLEWARE)
$(HELM) upgrade --install $(OPENBAO_RELEASE) openbao/openbao \
--version $(OPENBAO_CHART_VERSION) \
--namespace $(OPENBAO_NAMESPACE) \
-f $(OPENBAO_VALUES) \
--wait --timeout 5m
$(MAKE) openbao-overlay-apply
openbao-status: ## Show OpenBao pods, services, PVCs, and seal/init status
$(KUBECTL) get pods,svc,pvc -n $(OPENBAO_NAMESPACE) \
-l app.kubernetes.io/instance=$(OPENBAO_RELEASE) -o wide
-$(KUBECTL) exec -n $(OPENBAO_NAMESPACE) $(OPENBAO_RELEASE)-0 -- bao status
openbao-verify: ## Run non-secret OpenBao deployment checks
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify.sh basic
openbao-verify-post-unseal: ## Run post-unseal OpenBao filesystem checks
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify.sh post-unseal
openbao-configure-initial: ## Apply first post-unseal audit, auth, mounts, and policies
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-apply-initial-config.sh
openbao-configure-ssh: ## Enable SSH secrets engine, roles, and warden-sign policy
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-apply-ssh-engine.sh
openbao-verify-ssh: ## Verify SSH engine mount, roles, and warden-sign policy
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify-ssh-engine.sh
openbao-verify-authenticated: ## Run authenticated non-mutating OpenBao audit/auth/mount checks
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify-authenticated.sh $(OPENBAO_VERIFY_AUTH_ARGS)
openbao-configure-external-secrets-issue-core: ## Configure OpenBao policy/role for issue-core ESO pilot
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) ESO_NAMESPACE=$(EXTERNAL_SECRETS_NAMESPACE) \
scripts/openbao-apply-external-secrets-issue-core.sh
openbao-configure-external-secrets-activity-core: ## Configure OpenBao policy/role for activity-core ESO lane
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) OPENBAO_RELEASE=$(OPENBAO_RELEASE) ESO_NAMESPACE=$(EXTERNAL_SECRETS_NAMESPACE) OPENBAO_ESO_ROLE=external-secrets-activity-core OPENBAO_ESO_POLICY=external-secrets-activity-core POLICY_FILE='$(CURDIR)/openbao/policies/external-secrets-activity-core.hcl' OPENBAO_ESO_NEXT_PATH=platform/workloads/issue-core/issue-core/issue-core-runtime OPENBAO_ESO_NEXT_FIELDS=ISSUE_CORE_API_KEY OPENBAO_ESO_NEXT_TARGET=ExternalSecret/activity-core/actcore-issue-core-runtime scripts/openbao-apply-external-secrets-issue-core.sh
openbao-configure-external-secrets-forgejo: ## Configure OpenBao policy/role for Forgejo mailer ESO lane
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) ESO_NAMESPACE=$(EXTERNAL_SECRETS_NAMESPACE) \
OPENBAO_ESO_ROLE=external-secrets-forgejo \
OPENBAO_ESO_POLICY=external-secrets-forgejo \
POLICY_FILE='$(CURDIR)/openbao/policies/external-secrets-forgejo.hcl' \
OPENBAO_ESO_NEXT_PATH=platform/workloads/forgejo/forgejo-mailer \
OPENBAO_ESO_NEXT_FIELDS=MAILER_PASSWD \
OPENBAO_ESO_NEXT_TARGET=ExternalSecret/forgejo/forgejo-mailer \
scripts/openbao-apply-external-secrets-issue-core.sh
openbao-workload-kv-lanes-dry-run: ## Dry-run OpenBao workload KV read-lane policy apply
scripts/openbao-apply-workload-kv-lanes.sh --dry-run $(OPENBAO_WORKLOAD_KV_ARGS)
openbao-configure-workload-kv-lanes: ## Configure OpenBao workload KV read-lane policies
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
scripts/openbao-apply-workload-kv-lanes.sh $(OPENBAO_WORKLOAD_KV_ARGS)
openbao-validate-restore-evidence: ## Validate non-secret OpenBao restore-drill evidence JSON
OPENBAO_RESTORE_EVIDENCE='$(OPENBAO_RESTORE_EVIDENCE)' \
scripts/openbao-validate-restore-evidence.sh
openbao-validate-emergency-evidence: ## Validate non-secret OpenBao emergency seal/unseal drill evidence JSON
OPENBAO_EMERGENCY_EVIDENCE='$(OPENBAO_EMERGENCY_EVIDENCE)' \
scripts/openbao-validate-emergency-drill-evidence.sh
##@ Credential broker
credential-grants-validate: ## Validate non-secret credential grant catalog
scripts/credential-grants-validate.py $(CREDENTIAL_GRANTS)
credential-change-validate: ## Validate non-secret credential change requests
scripts/credential-change.py validate
credential-change-render: ## Render a credential change request review summary
scripts/credential-change.py render $(CREDENTIAL_CHANGE)
credential-change-plan: ## Render a credential change request apply plan for review
scripts/credential-change.py plan $(CREDENTIAL_CHANGE)
credential-change-decision-templates: ## Render CCR approve/deny/needs-changes templates
scripts/credential-change.py decision-templates $(CREDENTIAL_CHANGE)
credential-change-status: ## Render credential change request readiness status
scripts/credential-change.py status $(CREDENTIAL_CHANGE)
credential-change-status-json: ## Render credential change request readiness status as JSON
scripts/credential-change.py status --json $(CREDENTIAL_CHANGE)
credential-change-sync-decision: ## Sync resolved State Hub decision back into a CCR
scripts/credential-change.py sync-decision $(CREDENTIAL_CHANGE) --state-hub-url $(STATE_HUB_URL)
credential-change-apply-plan: ## Render approved-only operator apply plan
scripts/credential-change.py apply-plan $(CREDENTIAL_CHANGE)
credential-change-operator-commands: ## Render approved-only non-secret OpenBao operator commands
scripts/credential-change.py operator-commands $(CREDENTIAL_CHANGE)
credential-change-applier-dry-run: ## Validate delegated OpenBao metadata mutations for a CCR
scripts/credential-change.py applier-dry-run $(CREDENTIAL_CHANGE)
credential-change-applier-apply-plan: ## Render delegated OpenBao metadata apply plan
scripts/credential-change.py applier-apply $(CREDENTIAL_CHANGE) --plan-only
credential-change-applier-apply: ## Apply delegated metadata; pass confirmation/actor args via CREDENTIAL_CHANGE_EVIDENCE_ARGS
scripts/credential-change.py applier-apply $(CREDENTIAL_CHANGE) $(CREDENTIAL_CHANGE_EVIDENCE_ARGS)
credential-change-runbook: ## Render the attended CCR apply/verify runbook
scripts/credential-change.py runbook $(CREDENTIAL_CHANGE)
credential-change-record-evidence: ## Record non-secret CCR evidence; pass CREDENTIAL_CHANGE_EVIDENCE_ARGS
scripts/credential-change.py record-evidence $(CREDENTIAL_CHANGE) $(CREDENTIAL_CHANGE_EVIDENCE_ARGS)
credential-change-lifecycle-plan: ## Render deactivation/rotation/compromise lifecycle guidance
scripts/credential-change.py lifecycle-plan $(CREDENTIAL_CHANGE) --action $(CREDENTIAL_CHANGE_LIFECYCLE_ACTION)
credential-change-lifecycle-event: ## Record lifecycle event; pass CREDENTIAL_CHANGE_LIFECYCLE_ARGS
scripts/credential-change.py lifecycle-event $(CREDENTIAL_CHANGE) --action $(CREDENTIAL_CHANGE_LIFECYCLE_ACTION) $(CREDENTIAL_CHANGE_LIFECYCLE_ARGS)
credential-change-import-inventory: ## Import existing lane as non-secret CCR; pass CREDENTIAL_CHANGE_IMPORT_ARGS
scripts/credential-change.py import-inventory $(CREDENTIAL_CHANGE_IMPORT_ARGS)
openbao-credential-change-appliers-dry-run: ## Dry-run credential-change applier policies/token roles
scripts/openbao-apply-credential-change-appliers.py --dry-run $(OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS)
openbao-configure-credential-change-appliers: ## Apply credential-change applier policies/token roles
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
scripts/openbao-apply-credential-change-appliers.py $(OPENBAO_CREDENTIAL_CHANGE_APPLIER_ARGS)
openbao-token-grants-dry-run: ## Dry-run OpenBao token roles and issuer policies for credential grants
scripts/openbao-apply-token-grants.py --dry-run $(OPENBAO_TOKEN_GRANT_ARGS)
openbao-configure-token-grants: ## Apply OpenBao token roles and issuer policies for credential grants
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
scripts/openbao-apply-token-grants.py $(OPENBAO_TOKEN_GRANT_ARGS)
openbao-verify-token-grants-dry-run: ## Dry-run OpenBao token grant verification
scripts/openbao-verify-token-grants.py --dry-run $(OPENBAO_TOKEN_GRANT_ARGS)
openbao-verify-token-grants: ## Verify OpenBao token roles and issuer policies for credential grants
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
scripts/openbao-verify-token-grants.py $(OPENBAO_TOKEN_GRANT_ARGS)
openbao-verify-token-grants-smoke: ## Mint/revoke a child token and prove bounded warden-sign capabilities
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
scripts/openbao-verify-token-grants.py --issue-smoke-token $(OPENBAO_TOKEN_GRANT_ARGS)
credential-helper-dry-run: ## Dry-run credential request, exec, status, and revoke helper flows
scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) request --dry-run \
--grant ops-warden/warden-sign --purpose $(CREDENTIAL_HELPER_PURPOSE) \
$(CREDENTIAL_HELPER_ARGS)
scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) request --dry-run \
--grant ops-warden/warden-sign --purpose $(CREDENTIAL_HELPER_PURPOSE) \
--delivery kubernetes-auth $(CREDENTIAL_HELPER_ARGS)
scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) exec --dry-run \
--grant ops-warden/warden-sign --purpose $(CREDENTIAL_HELPER_PURPOSE) \
$(CREDENTIAL_HELPER_ARGS) -- SMOKE_VAULT=1 /bin/true
scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) status --dry-run example-accessor
scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) revoke --dry-run example-accessor
credential-tests: ## Run offline credential broker unit tests
python3 -m unittest discover -s tests -p 'test_credential*.py'
credential-change-tests: ## Run credential change request unit tests
python3 -m unittest discover -s tests -p 'test_credential_change.py'
credential-exec-ops-warden-smoke: ## Run ops-warden smoke with an exec-injected warden-sign token
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) \
scripts/credential.py $(CREDENTIAL_HELPER_GLOBAL_ARGS) exec \
--grant ops-warden/warden-sign --purpose ops-warden-production-sign-smoke \
$(CREDENTIAL_HELPER_ARGS) -- \
$(CREDENTIAL_HELPER_CHILD_ENV) \
SMOKE_VAULT=1 /home/worsch/ops-warden/scripts/policy_gate_production_smoke.sh
##@ ArgoCD GitOps bootstrap
argocd-bootstrap-dry-run: ## Server-side dry-run ArgoCD AppProjects and root Application
$(KUBECTL) apply --dry-run=server -k $(ARGOCD_BOOTSTRAP_DIR)
argocd-bootstrap-deploy: ## Apply ArgoCD AppProjects and root Application
$(KUBECTL) apply -k $(ARGOCD_BOOTSTRAP_DIR)
argocd-repo-apply: ## Apply a SOPS-encrypted ArgoCD repository Secret (set ARGOCD_REPOSITORY_SECRET)
@test -n "$(ARGOCD_REPOSITORY_SECRET)" || \
(echo "ERROR: set ARGOCD_REPOSITORY_SECRET=argocd/repositories/<repo>.repository.sops.yaml"; exit 1)
sops -d $(ARGOCD_REPOSITORY_SECRET) | $(KUBECTL) apply -f -
argocd-status: ## Show Railiance ArgoCD projects, root app, and registered repos
$(KUBECTL) get appprojects.argoproj.io -n $(ARGOCD_NAMESPACE) \
railiance-bootstrap railiance-tenants railiance-platform-addons
$(KUBECTL) get applications.argoproj.io -n $(ARGOCD_NAMESPACE) \
railiance-apps-root external-secrets openbao-secretstore issue-core
$(KUBECTL) get secrets -n $(ARGOCD_NAMESPACE) \
-l argocd.argoproj.io/secret-type=repository
##@ Backup
RAILIANCE01_KUBECONFIG ?= $(HOME)/.kube/config-railiance01
backup: ## Backup platform services (PostgreSQL logical dump) — age-encrypted to Nextcloud
@test -x tools/cmd/railiance-backup || { echo "tools/cmd/railiance-backup not installed; use forgejo-backup for Forgejo." >&2; exit 1; }
sudo tools/cmd/railiance-backup
forgejo-backup: ## Forgejo dump + forgejo-db pg_dump → age → Nextcloud (T04/T09 Option A)
KUBECONFIG="$(RAILIANCE01_KUBECONFIG)" tools/cmd/forgejo-backup
forgejo-backup-dry-run: ## Forgejo backup without Nextcloud upload
KUBECONFIG="$(RAILIANCE01_KUBECONFIG)" RAILIANCE_BACKUP_DRY_RUN=1 tools/cmd/forgejo-backup
forgejo-backup-status: ## Show last Forgejo backup success and 7-day gate
@STAMP="$(HOME)/.cache/railiance/backups/forgejo/success-log"; \
if [[ ! -f "$$STAMP" ]]; then echo "No forgejo backups recorded yet."; exit 1; fi; \
echo "Last success: $$(tail -1 "$$STAMP")"; \
echo "Recent successes:"; tail -7 "$$STAMP"; \
echo "7-day gate: $$(tail -7 "$$STAMP" | wc -l)/7 consecutive days logged (verify cron separately)"
forgejo-package-prune-dry-run: ## List Forgejo package versions beyond retention depth (no deletes)
tools/cmd/forgejo-package-prune
forgejo-package-prune: ## Prune Forgejo packages — keep newest 3 versions per package
tools/cmd/forgejo-package-prune --apply
##@ Family declarations
reef-deployables: ## Capture live reef-railiance deployables for the family validator
python3 scripts/capture-reef-deployables.py -o docs/evidence/reef-railiance-deployables.json
apps-pg-evidence: ## Capture non-secret apps-pg capacity evidence for resource-control
python3 scripts/capture-apps-pg-evidence.py -o docs/evidence/RAILIANCE-WP-0016-apps-pg-observation.json
##@ Help
help: ## Show this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} \
/^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-22s\033[0m %s\n", $$1, $$2 } \
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
.PHONY: apps-pg-isolation-probe
.PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run reef-deployables apps-pg-evidence consumption-preflight help