railiance-platform/Makefile
codex b1f973c2d5
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Pin apps-pg targets to railiance01 by cluster identity; seed RPF-WP-0020
Two reachable clusters each carry a CNPG Cluster named apps-pg in a
namespace named databases. KUBECONFIG is an environment variable, so the
Makefile ?= default never applied, and RAILIANCE01_KUBECONFIG pointed at
config-hosteurope - a different cluster. Had the environment pointed at the
other reachable cluster instead of an unauthorized one, make apps-pg-deploy
would have applied RPF-WP-0019 connection limits, role timeouts and backup
config to the wrong cluster and reported success. The Unauthorized error was
the only thing that prevented it.

Filename selection cannot protect against this: both kubeconfigs resolve to
a 127.0.0.1 tunnel port and the environment wins either way. railiance01-guard
pins identity instead, comparing the live kube-system namespace UID against
RAILIANCE01_CLUSTER_UID, and fails closed on mismatch or unreachability. It
gates apps-pg deploy, backup-deploy, overflow-dry-run, status and shell.
Verified refusing on the wrong cluster, refusing when unreachable, and
passing on railiance01. Not global: db-status legitimately targets the other
cluster for gitea-db.

RPF-WP-0019 blocker note corrected - the cluster was never unreachable, our
wiring was wrong.

RPF-WP-0020 seeded for the pre-existing CCR test failure, which is two
unrelated problems: CCR-2026-0010 is an active lane missing its whole
openbao.auth block, and CCR-2026-0011 is an honest in-flight draft the suite
has no way to express.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:18:37 +02:00

463 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-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: 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