Compare commits
1 Commits
codex/l4-w
...
codex/s57-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed9b989fa |
@@ -113,12 +113,7 @@ spec:
|
||||
- name: pgdata
|
||||
mountPath: /var/lib/postgresql/data
|
||||
volumeClaimTemplates:
|
||||
# apiVersion/kind included deliberately: this STS was created via ArgoCD ServerSideApply,
|
||||
# so the live object carries PVC TypeMeta inside volumeClaimTemplates; omitting it here
|
||||
# leaves the app eternally OutOfSync even though kubectl SSA dry-run shows no change.
|
||||
- apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
- metadata:
|
||||
name: pgdata
|
||||
spec:
|
||||
storageClassName: longhorn
|
||||
|
||||
@@ -46,8 +46,6 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/healthz"
|
||||
prometheus.io/path: /metrics/prometheus
|
||||
prometheus.io/port: "5000"
|
||||
prometheus.io/scrape: "true"
|
||||
@@ -56,7 +54,6 @@ spec:
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
containers:
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
- envFrom:
|
||||
- configMapRef:
|
||||
name: aistation-web-config
|
||||
@@ -170,26 +167,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: aistation-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose aistation-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: aistation-web-public
|
||||
# namespace: fc-aistation
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`aistation.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: aistation-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: aistation-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -112,8 +112,6 @@ spec:
|
||||
app.kubernetes.io/name: chat-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/healthz"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics/prometheus"
|
||||
@@ -130,7 +128,6 @@ spec:
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: chat-web-config
|
||||
|
||||
@@ -14,20 +14,6 @@
|
||||
# cluster-rebuild repeatability. See
|
||||
# feedback_networkpolicies_belong_in_bluejay_infra.md.
|
||||
---
|
||||
# OIDC client secret for the RemoteDesktop end-user sign-in (fleet regroup L9,
|
||||
# 2026-06-12). The Authentik provider `remotedesktop` already exists; the 1P item
|
||||
# `remotedesktop-oidc-client` (vault IAmWorkin) carries issuer_url / client_id /
|
||||
# client_secret, and the 1Password operator mints the same-named K8s Secret that
|
||||
# k8s/web-deployment.yaml (FlowerCore.RemoteDesktop repo) consumes with
|
||||
# optional:true. Gate stays OFF (Q-RD-16) — this is flip-READINESS only.
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: remotedesktop-oidc-client
|
||||
namespace: fc-desktop
|
||||
spec:
|
||||
itemPath: "vaults/IAmWorkin/items/remotedesktop-oidc-client"
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
@@ -65,26 +51,3 @@ spec:
|
||||
port: 8080
|
||||
tls:
|
||||
secretName: remotedesktop-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose remotedesktop-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: remotedesktop-web-public
|
||||
# namespace: fc-desktop
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`desktop.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: remotedesktop-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: remotedesktop-web
|
||||
# port: 8080
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -11,7 +11,7 @@ metadata:
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
rules:
|
||||
- apiGroups:
|
||||
- flowercore.io
|
||||
- devices.flowercore.io
|
||||
resources:
|
||||
- '*'
|
||||
verbs:
|
||||
@@ -23,7 +23,7 @@ rules:
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- flowercore.io
|
||||
- devices.flowercore.io
|
||||
resources:
|
||||
- devices/status
|
||||
- devices/finalizers
|
||||
@@ -33,8 +33,6 @@ rules:
|
||||
- devicepolicies/finalizers
|
||||
- remotecommands/status
|
||||
- remotecommands/finalizers
|
||||
- desiredstatedocuments/status
|
||||
- desiredstatedocuments/finalizers
|
||||
verbs:
|
||||
- get
|
||||
- update
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
# FlowerCore.DeviceManagement CRDs.
|
||||
#
|
||||
# These CRDs match the current operator annotations:
|
||||
# [KubernetesEntity(Group = "flowercore.io", ApiVersion = "v1alpha1", ...)]
|
||||
# Keep the schemas intentionally permissive until the DeviceManagement operator
|
||||
# grows enforced CRD validation.
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: devices.flowercore.io
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||
app.kubernetes.io/component: operator
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
spec:
|
||||
group: flowercore.io
|
||||
scope: Namespaced
|
||||
names:
|
||||
plural: devices
|
||||
singular: device
|
||||
kind: Device
|
||||
listKind: DeviceList
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
status:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: devicegroups.flowercore.io
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||
app.kubernetes.io/component: operator
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
spec:
|
||||
group: flowercore.io
|
||||
scope: Namespaced
|
||||
names:
|
||||
plural: devicegroups
|
||||
singular: devicegroup
|
||||
kind: DeviceGroup
|
||||
listKind: DeviceGroupList
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
status:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: devicepolicies.flowercore.io
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||
app.kubernetes.io/component: operator
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
spec:
|
||||
group: flowercore.io
|
||||
scope: Namespaced
|
||||
names:
|
||||
plural: devicepolicies
|
||||
singular: devicepolicy
|
||||
kind: DevicePolicy
|
||||
listKind: DevicePolicyList
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
status:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: remotecommands.flowercore.io
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||
app.kubernetes.io/component: operator
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
spec:
|
||||
group: flowercore.io
|
||||
scope: Namespaced
|
||||
names:
|
||||
plural: remotecommands
|
||||
singular: remotecommand
|
||||
kind: RemoteCommand
|
||||
listKind: RemoteCommandList
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
status:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: desiredstatedocuments.flowercore.io
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||
app.kubernetes.io/component: operator
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
spec:
|
||||
group: flowercore.io
|
||||
scope: Namespaced
|
||||
names:
|
||||
plural: desiredstatedocuments
|
||||
singular: desiredstatedocument
|
||||
kind: DesiredStateDocument
|
||||
listKind: DesiredStateDocumentList
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
status:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
@@ -5,35 +5,21 @@
|
||||
# exist yet; import localhost/fc-devicemgmt-web:<tag> to all schedulable RKE2
|
||||
# nodes before letting ArgoCD sync a live rollout.
|
||||
#
|
||||
# LIVE — 2026-06-11 DeviceManagement product-host enablement.
|
||||
# The current DeviceManagement Web source is SQLite-backed in Program.cs, so
|
||||
# Phase 1 production uses a Longhorn RWO PVC at /data/devicemgmt.db. The
|
||||
# 1Password runtime item stays mounted through env for future MySQL/API-key
|
||||
# cutover, but MySQL is not required for this first product-host rollout.
|
||||
# Image v20260611-healthz is built from FlowerCore.DeviceManagement master
|
||||
# 3c15f3b, which adds the /healthz alias required by fleet monitoring.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: fc-devicemgmt-web-data
|
||||
namespace: fc-devicemgmt
|
||||
labels:
|
||||
app: fc-devicemgmt-web
|
||||
app.kubernetes.io/name: fc-devicemgmt-web
|
||||
app.kubernetes.io/component: web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
# SCALED TO 0 — 2026-05-19 morning-routine cleanup.
|
||||
# The Web pod cannot start until TWO upstream gaps close:
|
||||
# 1. MySQL DB instance `flowercore_devicemgmt` (user `fc_devicemgmt`) is
|
||||
# provisioned via fc-mysql Manager. The cluster currently has ZERO
|
||||
# MySqlInstanceCrds and no `mysql.fc-mysql.svc:3306` Service, so the
|
||||
# deployment-web container env `FlowerCore__Database__Host=mysql.fc-mysql.svc`
|
||||
# points at nothing. Provision via the fc-mysql Manager UI/REST/MCP.
|
||||
# 2. 1Password vault item `IAmWorkin/FlowerCore DeviceManagement Runtime`
|
||||
# with 5 fields (DB-Password, mtls-ca.pem, mtls-client.crt, mtls-client.key,
|
||||
# mtls-chain.pem) — see apps/fc-devicemgmt/1password-item.yaml. Mint mTLS
|
||||
# from step-ca-agent ClusterIssuer per ADR-126; DB-Password must match the
|
||||
# password configured for the MySQL user.
|
||||
# Re-enable: change replicas back to 2 after both gaps close. The image tag
|
||||
# in this file (v20260512-cx5) MAY also need a refresh — it predates the
|
||||
# Sprint 34 Cl-3 operator fix; Web may have an analogous bug.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -50,7 +36,7 @@ metadata:
|
||||
annotations:
|
||||
flowercore.io/traceability-standard: k8s-pod-ownership-and-traceability-standard
|
||||
spec:
|
||||
replicas: 1
|
||||
replicas: 0
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
@@ -66,8 +52,6 @@ spec:
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/healthz"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
@@ -78,12 +62,11 @@ spec:
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
containers:
|
||||
- name: web
|
||||
image: localhost/fc-devicemgmt-web:v20260611-healthz
|
||||
image: localhost/fc-devicemgmt-web:v20260512-cx5
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: "http://+:8080"
|
||||
@@ -91,21 +74,29 @@ spec:
|
||||
value: "Production"
|
||||
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||
value: "false"
|
||||
- name: HOME
|
||||
value: "/data"
|
||||
- name: FlowerCore__Service__Name
|
||||
value: "FlowerCore.DeviceManagement.Web"
|
||||
- name: FlowerCore__DeviceManagement__DefaultTenantId
|
||||
value: "system"
|
||||
- name: FlowerCore__Database__Provider
|
||||
value: "Sqlite"
|
||||
- name: FlowerCore__Database__ConnectionStrings__Sqlite
|
||||
value: "Data Source=/data/devicemgmt.db"
|
||||
value: "MySql"
|
||||
- name: FlowerCore__Database__Host
|
||||
value: "mysql.fc-mysql.svc"
|
||||
- name: FlowerCore__Database__Database
|
||||
value: "flowercore_devicemgmt"
|
||||
- name: FlowerCore__Database__User
|
||||
value: "fc_devicemgmt"
|
||||
- name: FlowerCore__Database__Password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: fc-devicemgmt-runtime
|
||||
key: DB-Password
|
||||
- name: FlowerCore__DeviceManagement__AgentMtls__CaPath
|
||||
value: "/secrets/devicemgmt-mtls/mtls-ca.pem"
|
||||
- name: FlowerCore__DeviceManagement__AgentMtls__ClientCertificatePath
|
||||
value: "/secrets/devicemgmt-mtls/mtls-client.crt"
|
||||
- name: FlowerCore__DeviceManagement__AgentMtls__ClientKeyPath
|
||||
value: "/secrets/devicemgmt-mtls/mtls-client.key"
|
||||
- name: FlowerCore__EventBus__Redis__Configuration
|
||||
value: "redis.fc-redis.svc:6379"
|
||||
resources:
|
||||
@@ -142,17 +133,19 @@ spec:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: logs
|
||||
mountPath: /app/logs
|
||||
- name: devicemgmt-mtls
|
||||
mountPath: /secrets/devicemgmt-mtls
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: fc-devicemgmt-web-data
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: logs
|
||||
emptyDir: {}
|
||||
- name: devicemgmt-mtls
|
||||
secret:
|
||||
secretName: fc-devicemgmt-runtime
|
||||
defaultMode: 0400
|
||||
|
||||
@@ -74,14 +74,6 @@ metadata:
|
||||
spec:
|
||||
itemPath: "vaults/IAmWorkin/items/FlowerCore Edition Signing Key - edition:aistation-field"
|
||||
---
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: distribution-oidc-client
|
||||
namespace: fc-distribution
|
||||
spec:
|
||||
itemPath: "vaults/IAmWorkin/items/distribution-oidc-client"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -109,7 +101,6 @@ spec:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
||||
spec:
|
||||
# Synology NFS export `/volume1/kubernetes` ACL only allows rke2-server
|
||||
# (10.0.56.11) right now. Until the ACL is widened in DSM (admin only),
|
||||
@@ -127,7 +118,7 @@ spec:
|
||||
# dotnet.exe publish -c Release -o deploy/app \
|
||||
# src/FlowerCore.Distribution.Web/FlowerCore.Distribution.Web.csproj
|
||||
# podman build -t localhost/fc-distribution:v<tag> -f deploy/Dockerfile.deploy deploy
|
||||
image: localhost/fc-distribution:v20260604-oidc-root-anon
|
||||
image: localhost/fc-distribution:v202605061948
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
@@ -139,25 +130,6 @@ spec:
|
||||
value: "Production"
|
||||
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||
value: "false"
|
||||
# Authentik/OIDC enforcement. Public read/entitlement + the
|
||||
# dist.flowercore.io Method() allowlist stay open; OIDC gates the
|
||||
# operator/admin surface while /healthz remains anonymous.
|
||||
- name: FlowerCore__Auth__Enabled
|
||||
value: "true"
|
||||
- name: FlowerCore__Auth__Oidc__Enabled
|
||||
value: "true"
|
||||
- name: FlowerCore__Auth__Oidc__Authority
|
||||
value: "https://id.iamworkin.lan/application/o/distribution/"
|
||||
- name: FlowerCore__Auth__Oidc__Audience
|
||||
value: "distribution"
|
||||
- name: FlowerCore__Auth__Oidc__ClientId
|
||||
value: "distribution"
|
||||
- name: FlowerCore__Auth__Oidc__ClientSecret
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: distribution-oidc-client
|
||||
key: client_secret
|
||||
optional: true
|
||||
# SQLite connection (catalog + data-protection keys via FlowerCoreDbContext).
|
||||
# Read by Data/DatabaseProviderExtensions.cs in precedence order; Sqlite key wins.
|
||||
- name: FlowerCore__Database__Provider
|
||||
|
||||
@@ -30,26 +30,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: dms-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose dms-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: dms-web-public
|
||||
# namespace: fc-dms
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`dms.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: dms-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: dms-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -1,481 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: fc-dns
|
||||
labels:
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
---
|
||||
# 1Password-backed Secret for the pfSense admin password.
|
||||
# The operator watches this CRD, resolves the vault item, and produces a
|
||||
# K8s Secret of the same name with each 1P field as a key. The `password`
|
||||
# field of the "pfSense Admin" item becomes Secret key `password`.
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: pfsense-admin
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
itemPath: "vaults/IAmWorkin/items/pfSense Admin"
|
||||
---
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: dns-oidc-client
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
itemPath: "vaults/IAmWorkin/items/dns-oidc-client"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: dns-web-data
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: dns-web-config
|
||||
namespace: fc-dns
|
||||
data:
|
||||
appsettings.Production.json: |
|
||||
{
|
||||
"FlowerCore": {
|
||||
"Auth": {
|
||||
"Enabled": false,
|
||||
"Oidc": {
|
||||
"Enabled": true,
|
||||
"Audience": "dns",
|
||||
"RequireHttpsMetadata": true
|
||||
}
|
||||
},
|
||||
"Database": {
|
||||
"Provider": "Sqlite",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=/data/dns.db"
|
||||
}
|
||||
},
|
||||
"Tenant": {
|
||||
"DefaultTenantId": "default",
|
||||
"JwtClaimsEnabled": false,
|
||||
"DefaultTenantHosts": [
|
||||
"dns.iamworkin.lan"
|
||||
]
|
||||
},
|
||||
"Audit": {
|
||||
"HashChain": {
|
||||
"BridgeSensitivity": {
|
||||
"Distribution": "Warn"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dns-web
|
||||
namespace: fc-dns
|
||||
labels:
|
||||
app.kubernetes.io/name: dns-web
|
||||
app.kubernetes.io/managed-by: flowercore
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: dns-web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: dns-web
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "5320"
|
||||
prometheus.io/path: "/metrics/prometheus"
|
||||
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
||||
spec:
|
||||
serviceAccountName: dns-web
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1654
|
||||
runAsGroup: 1654
|
||||
fsGroup: 1654
|
||||
containers:
|
||||
- name: dns-web
|
||||
image: localhost/fc-dns-web:v20260612-l4dns-a5d2849
|
||||
imagePullPolicy: Never
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
ports:
|
||||
- containerPort: 5320
|
||||
env:
|
||||
# pfSense admin password resolved by the 1Password operator.
|
||||
# `FallbackPassword` is the Slice A seam exposed by
|
||||
# OptionsFallbackPasswordResolver; Slice B will replace it with
|
||||
# a pull-at-runtime 1P Connect resolver once Shared.Vault ships.
|
||||
- name: FlowerCore__Dns__Providers__PfSenseUnbound__FallbackPassword
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: pfsense-admin
|
||||
key: password
|
||||
- name: FlowerCore__Auth__Oidc__Authority
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dns-oidc-client
|
||||
key: issuer_url
|
||||
optional: true
|
||||
- name: FlowerCore__Auth__Oidc__ClientId
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dns-oidc-client
|
||||
key: client_id
|
||||
optional: true
|
||||
- name: FlowerCore__Auth__Oidc__ClientSecret
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dns-oidc-client
|
||||
key: client_secret
|
||||
optional: true
|
||||
- name: FlowerCore__Auth__Enabled
|
||||
value: "false"
|
||||
- name: FlowerCore__Auth__Oidc__Enabled
|
||||
value: "true"
|
||||
- name: FlowerCore__Auth__Oidc__Audience
|
||||
value: "dns"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: logs
|
||||
mountPath: /app/logs
|
||||
- name: config
|
||||
mountPath: /app/appsettings.Production.json
|
||||
subPath: appsettings.Production.json
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 96Mi
|
||||
limits:
|
||||
cpu: 300m
|
||||
memory: 384Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 5320
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 5320
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 30
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: dns-web-data
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: logs
|
||||
emptyDir: {}
|
||||
- name: config
|
||||
configMap:
|
||||
name: dns-web-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: dns-web
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: dns-web
|
||||
ports:
|
||||
- port: 5320
|
||||
targetPort: 5320
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: dns-web
|
||||
namespace: fc-dns
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: dns-web
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces", "pods", "services", "secrets", "configmaps"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["cert-manager.io"]
|
||||
resources: ["certificates"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: dns-web
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: dns-web
|
||||
namespace: fc-dns
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: dns-web
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: dns-web-cert
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
secretName: dns-web-tls
|
||||
issuerRef:
|
||||
name: step-ca-dns01
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- dns.iamworkin.lan
|
||||
duration: 720h
|
||||
renewBefore: 240h
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: dns-web
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
entryPoints: [websecure]
|
||||
routes:
|
||||
- match: Host(`dns.iamworkin.lan`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: dns-web
|
||||
port: 5320
|
||||
tls:
|
||||
secretName: dns-web-tls
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: dns-acme-webhook
|
||||
namespace: fc-dns
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dns-acme-webhook
|
||||
namespace: fc-dns
|
||||
labels:
|
||||
app.kubernetes.io/name: dns-acme-webhook
|
||||
app.kubernetes.io/managed-by: flowercore
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: dns-acme-webhook
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: dns-acme-webhook
|
||||
spec:
|
||||
serviceAccountName: dns-acme-webhook
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1654
|
||||
runAsGroup: 1654
|
||||
fsGroup: 1654
|
||||
containers:
|
||||
- name: dns-acme-webhook
|
||||
image: localhost/fc-dns-acme-webhook:v20260612-l4dns-a5d2849
|
||||
imagePullPolicy: Never
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
ports:
|
||||
- containerPort: 9443
|
||||
name: https
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: https://+:9443
|
||||
- name: Kestrel__Certificates__Default__Path
|
||||
value: /tls/tls.crt
|
||||
- name: Kestrel__Certificates__Default__KeyPath
|
||||
value: /tls/tls.key
|
||||
- name: FlowerCore__Dns__AcmeWebhook__ServiceBaseUrl
|
||||
value: http://dns-web:5320
|
||||
- name: FlowerCore__Dns__AcmeWebhook__GroupName
|
||||
value: acme.flowercore.io
|
||||
- name: FlowerCore__Dns__AcmeWebhook__SolverName
|
||||
value: flowercore-dns
|
||||
- name: FlowerCore__Dns__AcmeWebhook__Version
|
||||
value: v1alpha1
|
||||
volumeMounts:
|
||||
- name: tls
|
||||
mountPath: /tls
|
||||
readOnly: true
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
- name: logs
|
||||
mountPath: /app/logs
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
scheme: HTTPS
|
||||
path: /readyz
|
||||
port: https
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
scheme: HTTPS
|
||||
path: /healthz
|
||||
port: https
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 5
|
||||
volumes:
|
||||
- name: tls
|
||||
secret:
|
||||
secretName: dns-acme-webhook-tls
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: logs
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: dns-acme-webhook
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: dns-acme-webhook
|
||||
ports:
|
||||
- port: 443
|
||||
targetPort: https
|
||||
name: https
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Issuer
|
||||
metadata:
|
||||
name: dns-acme-webhook-selfsigned
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
selfSigned: {}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: dns-acme-webhook-ca
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
secretName: dns-acme-webhook-ca
|
||||
duration: 43800h
|
||||
issuerRef:
|
||||
name: dns-acme-webhook-selfsigned
|
||||
commonName: ca.dns-acme-webhook.fc-dns
|
||||
isCA: true
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Issuer
|
||||
metadata:
|
||||
name: dns-acme-webhook-ca-issuer
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
ca:
|
||||
secretName: dns-acme-webhook-ca
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: dns-acme-webhook-serving-cert
|
||||
namespace: fc-dns
|
||||
spec:
|
||||
secretName: dns-acme-webhook-tls
|
||||
duration: 8760h
|
||||
issuerRef:
|
||||
name: dns-acme-webhook-ca-issuer
|
||||
dnsNames:
|
||||
- dns-acme-webhook
|
||||
- dns-acme-webhook.fc-dns
|
||||
- dns-acme-webhook.fc-dns.svc
|
||||
---
|
||||
apiVersion: apiregistration.k8s.io/v1
|
||||
kind: APIService
|
||||
metadata:
|
||||
name: v1alpha1.acme.flowercore.io
|
||||
annotations:
|
||||
cert-manager.io/inject-ca-from: fc-dns/dns-acme-webhook-serving-cert
|
||||
spec:
|
||||
group: acme.flowercore.io
|
||||
groupPriorityMinimum: 1000
|
||||
service:
|
||||
name: dns-acme-webhook
|
||||
namespace: fc-dns
|
||||
version: v1alpha1
|
||||
versionPriority: 15
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: dns-acme-webhook-solver
|
||||
rules:
|
||||
- apiGroups: ["acme.flowercore.io"]
|
||||
resources: ["flowercore-dns"]
|
||||
verbs: ["create"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: dns-acme-webhook-solver
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cert-manager
|
||||
namespace: cert-manager
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: dns-acme-webhook-solver
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: step-ca-dns01
|
||||
spec:
|
||||
acme:
|
||||
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ4RENDQVdxZ0F3SUJBZ0lSQVBZMzU3RzZvdzZ6TUFMNSs0YlMya2t3Q2dZSUtvWkl6ajBFQXdJd1FERWEKTUJnR0ExVUVDaE1SU1VGdFYyOXlhMmx1SUVGRFRVVWdRMEV4SWpBZ0JnTlZCQU1UR1VsQmJWZHZjbXRwYmlCQgpRMDFGSUVOQklGSnZiM1FnUTBFd0hoY05Nall3TXpBNE1UZ3dOekV4V2hjTk16WXdNekExTVRnd056RXhXakJBCk1Sb3dHQVlEVlFRS0V4RkpRVzFYYjNKcmFXNGdRVU5OUlNCRFFURWlNQ0FHQTFVRUF4TVpTVUZ0VjI5eWEybHUKSUVGRFRVVWdRMEVnVW05dmRDQkRRVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSjJuMDRYMQpKWm81WmRxL2kxSWR2OCtmcXdaeUF6Qmg3d2hicWowU1dzSkw4VVdSYWJDTXFZQ3M3K2RYTzB4UlN6cWt3RkRMCngrdm9vT2FpOFJnUk5oYWpSVEJETUE0R0ExVWREd0VCL3dRRUF3SUJCakFTQmdOVkhSTUJBZjhFQ0RBR0FRSC8KQWdFQk1CMEdBMVVkRGdRV0JCUm51UFBRUjZpTS9INnZPbHVpVTNTeWdheXo4akFLQmdncWhrak9QUVFEQWdOSQpBREJGQWlFQXJRSzlkWVBHbUFac2RZbmp6aXVGVlZFNU5LWlVjY2VZdkdmR0MrdExYVXNDSUF1ZEYyekpyQ1JxCjNtSzUwWlpFVC9md1RrSndpRUY0ODI0bWpQOHAxQ0tNCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||
privateKeySecretRef:
|
||||
name: step-ca-dns01-account-key
|
||||
server: https://10.0.56.10:9443/acme/acme/directory
|
||||
solvers:
|
||||
- dns01:
|
||||
webhook:
|
||||
groupName: acme.flowercore.io
|
||||
solverName: flowercore-dns
|
||||
@@ -1,6 +0,0 @@
|
||||
# ArgoCD's bluejay-infra ApplicationSet discovers apps/* directories on main.
|
||||
# The kustomization is included for local previews and single-app validation.
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- fc-dns.yaml
|
||||
@@ -46,8 +46,6 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/health"
|
||||
prometheus.io/path: /metrics/prometheus
|
||||
prometheus.io/port: "5000"
|
||||
prometheus.io/scrape: "true"
|
||||
@@ -56,7 +54,6 @@ spec:
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
containers:
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
- envFrom:
|
||||
- configMapRef:
|
||||
name: library-web-config
|
||||
@@ -170,26 +167,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: library-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose library-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: library-web-public
|
||||
# namespace: fc-library
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`library.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: library-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: library-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -83,8 +83,6 @@ spec:
|
||||
app.kubernetes.io/name: fc-llm-bridge
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/healthz"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
@@ -118,7 +116,6 @@ spec:
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: "http://+:8080"
|
||||
@@ -284,26 +281,3 @@ spec:
|
||||
port: 8080
|
||||
tls:
|
||||
secretName: fc-llm-bridge-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose fc-llm-bridge publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: fc-llm-bridge-public
|
||||
# namespace: fc-llm-bridge
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`llm-bridge.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: fc-llm-bridge-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: fc-llm-bridge
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: fc-media
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-media
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
---
|
||||
apiVersion: onepassword.com/v1
|
||||
kind: OnePasswordItem
|
||||
metadata:
|
||||
name: media-oidc-client
|
||||
namespace: fc-media
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
itemPath: "vaults/IAmWorkin/items/media-oidc-client"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: fc-media-config
|
||||
namespace: fc-media
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
data:
|
||||
appsettings.Production.json: |
|
||||
{
|
||||
"DatabaseProvider": "Sqlite",
|
||||
"ConnectionStrings": {
|
||||
"Sqlite": "Data Source=/data/media.db"
|
||||
},
|
||||
"FlowerCore": {
|
||||
"Auth": {
|
||||
"Enabled": true,
|
||||
"Oidc": {
|
||||
"Authority": "https://id.iamworkin.lan/application/o/media/",
|
||||
"ClientId": "media",
|
||||
"ClientSecret": "",
|
||||
"Audience": "media",
|
||||
"RequireHttpsMetadata": true
|
||||
}
|
||||
},
|
||||
"Tenant": {
|
||||
"JwtClaimsEnabled": false,
|
||||
"DefaultTenantHosts": [ "media.iamworkin.lan" ]
|
||||
}
|
||||
},
|
||||
"Media": {
|
||||
"LibraryRoot": "/media/library",
|
||||
"Sources": [
|
||||
{
|
||||
"Name": "BlueJayNAS Video",
|
||||
"Driver": "Nfs",
|
||||
"MountedPath": "/media/library",
|
||||
"RemotePath": "nfs://10.0.58.3/volume1/video",
|
||||
"IsEnabled": true,
|
||||
"IsDefault": true,
|
||||
"Notes": "Synology NFS media share mounted read-only inside the cluster."
|
||||
}
|
||||
],
|
||||
"GeneratedRoot": "/data/generated",
|
||||
"TranscodeRoot": "/data/transcodes",
|
||||
"InboxPath": "/media/inbox",
|
||||
"InboxScanIntervalMinutes": 5,
|
||||
"ScanOnStartup": false,
|
||||
"ComputeChecksums": false,
|
||||
"FfmpegCommand": "ffmpeg",
|
||||
"FfprobeCommand": "ffprobe",
|
||||
"Hls": {
|
||||
"MaxConcurrentJobs": 1
|
||||
},
|
||||
"DefaultViewerName": "BlueJay",
|
||||
"Dlna": {
|
||||
"IsEnabled": true,
|
||||
"MulticastAddress": "239.255.255.250",
|
||||
"Port": 1900,
|
||||
"DiscoveryTimeoutSeconds": 2,
|
||||
"DescriptionFetchTimeoutSeconds": 2,
|
||||
"MaxResponsesPerSearchTarget": 32,
|
||||
"SearchTargets": [
|
||||
"urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
"urn:schemas-upnp-org:device:MediaServer:1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: fc-media-data
|
||||
namespace: fc-media
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 20Gi
|
||||
storageClassName: longhorn
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: fc-media-web
|
||||
namespace: fc-media
|
||||
labels:
|
||||
app: fc-media-web
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: fc-media-web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: fc-media-web
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "5200"
|
||||
prometheus.io/path: "/metrics"
|
||||
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
||||
spec:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: rke2-server
|
||||
containers:
|
||||
- name: fc-media-web
|
||||
image: localhost/fc-media-web:v20260604-oidc-proper
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 5200
|
||||
name: http
|
||||
env:
|
||||
- name: ASPNETCORE_ENVIRONMENT
|
||||
value: Production
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://+:5200
|
||||
- name: FlowerCore__Auth__Enabled
|
||||
value: "true"
|
||||
- name: FlowerCore__Auth__Oidc__Enabled
|
||||
value: "true"
|
||||
- name: FlowerCore__Auth__Oidc__Audience
|
||||
value: "media"
|
||||
- name: FlowerCore__Auth__Oidc__ClientId
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: media-oidc-client
|
||||
key: client_id
|
||||
optional: true
|
||||
- name: FlowerCore__Auth__Oidc__ClientSecret
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: media-oidc-client
|
||||
key: client_secret
|
||||
optional: true
|
||||
- name: FlowerCore__Auth__Oidc__Authority
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: media-oidc-client
|
||||
key: issuer_url
|
||||
optional: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "4"
|
||||
memory: 4Gi
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /app/appsettings.Production.json
|
||||
subPath: appsettings.Production.json
|
||||
readOnly: true
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: transcodes
|
||||
mountPath: /data/transcodes
|
||||
- name: media-library
|
||||
mountPath: /media/library
|
||||
readOnly: true
|
||||
- name: media-inbox
|
||||
mountPath: /media/inbox
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 5200
|
||||
httpHeaders:
|
||||
- name: X-Forwarded-Proto
|
||||
value: https
|
||||
failureThreshold: 18
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 5200
|
||||
httpHeaders:
|
||||
- name: X-Forwarded-Proto
|
||||
value: https
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 5200
|
||||
httpHeaders:
|
||||
- name: X-Forwarded-Proto
|
||||
value: https
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: fc-media-config
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: fc-media-data
|
||||
- name: transcodes
|
||||
nfs:
|
||||
server: 10.0.58.3
|
||||
path: /volume1/kubernetes/fc-media-transcodes
|
||||
- name: media-inbox
|
||||
nfs:
|
||||
server: 10.0.58.3
|
||||
path: /volume1/kubernetes/fc-media-inbox
|
||||
- name: media-library
|
||||
nfs:
|
||||
server: 10.0.58.3
|
||||
path: /volume1/video
|
||||
readOnly: true
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: fc-media-web
|
||||
namespace: fc-media
|
||||
labels:
|
||||
app: fc-media-web
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: fc-media-web
|
||||
ports:
|
||||
- port: 5200
|
||||
targetPort: 5200
|
||||
protocol: TCP
|
||||
name: http
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: fc-media-tls
|
||||
namespace: fc-media
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
secretName: fc-media-tls
|
||||
issuerRef:
|
||||
name: step-ca-acme
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- media.iamworkin.lan
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: fc-media-web
|
||||
namespace: fc-media
|
||||
labels:
|
||||
app.kubernetes.io/name: fc-media-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`media.iamworkin.lan`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: fc-media-web
|
||||
port: 5200
|
||||
tls:
|
||||
secretName: fc-media-tls
|
||||
@@ -1,6 +0,0 @@
|
||||
# ArgoCD's bluejay-infra ApplicationSet discovers apps/* directories on main.
|
||||
# The kustomization is included for local previews and single-app validation.
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- fc-media.yaml
|
||||
@@ -30,26 +30,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: menuboard-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose menuboard-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: menuboard-web-public
|
||||
# namespace: fc-menuboard
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`menuboard.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: menuboard-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: menuboard-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -41,8 +41,6 @@ spec:
|
||||
labels:
|
||||
app: messageboard-web
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/health"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics/prometheus"
|
||||
@@ -54,7 +52,6 @@ spec:
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: messageboard-web-config
|
||||
@@ -144,26 +141,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: messageboard-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose messageboard-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: messageboard-web-public
|
||||
# namespace: fc-messageboard
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`messageboard.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: messageboard-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: messageboard-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -30,26 +30,3 @@ spec:
|
||||
port: 5300
|
||||
tls:
|
||||
secretName: mysql-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose mysql-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: mysql-web-public
|
||||
# namespace: fc-mysql
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`mysql.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: mysql-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: mysql-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -30,26 +30,3 @@ spec:
|
||||
port: 5400
|
||||
tls:
|
||||
secretName: php-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose php-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: php-web-public
|
||||
# namespace: fc-php
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`php.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: php-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: php-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -30,26 +30,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: presentations-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose presentations-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: presentations-web-public
|
||||
# namespace: fc-presentations
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`presentations.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: presentations-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: presentations-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -46,8 +46,6 @@ spec:
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/healthz"
|
||||
kubectl.kubernetes.io/restartedAt: "2026-06-02T01:34:08-05:00"
|
||||
prometheus.io/path: /metrics/prometheus
|
||||
prometheus.io/port: "5000"
|
||||
@@ -57,7 +55,6 @@ spec:
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
spec:
|
||||
containers:
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
- envFrom:
|
||||
- configMapRef:
|
||||
name: retail-web-config
|
||||
@@ -171,26 +168,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: retail-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose retail-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: retail-web-public
|
||||
# namespace: fc-retail
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`retail.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: retail-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: retail-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -30,26 +30,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: scoreboard-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose scoreboard-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: scoreboard-web-public
|
||||
# namespace: fc-scoreboard
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`scoreboard.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: scoreboard-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: scoreboard-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -37,26 +37,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: segmentdisplay-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose segmentdisplay-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: segmentdisplay-web-public
|
||||
# namespace: fc-segmentdisplay
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`segmentdisplay.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: segmentdisplay-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: segmentdisplay-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -46,26 +46,3 @@ spec:
|
||||
services:
|
||||
- name: signage-web
|
||||
port: 5190
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose signage-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: signage-web-public
|
||||
# namespace: fc-signage
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`signage.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: signage-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: signage-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -97,7 +97,6 @@ spec:
|
||||
containers:
|
||||
- name: piper
|
||||
image: rhasspy/wyoming-piper:latest
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
env:
|
||||
- name: PYTHONHTTPSVERIFY
|
||||
value: "0"
|
||||
@@ -524,8 +523,6 @@ spec:
|
||||
app.kubernetes.io/name: ttsreader-web
|
||||
app.kubernetes.io/part-of: flowercore
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/health"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "5217"
|
||||
prometheus.io/path: "/metrics"
|
||||
@@ -535,7 +532,7 @@ spec:
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
containers:
|
||||
- name: web
|
||||
image: localhost/fc-ttsreader-web:v20260612-readalong-corrections
|
||||
image: localhost/fc-ttsreader-web:v20260603-s54cx14-pr29-schema
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 5217
|
||||
@@ -765,26 +762,3 @@ spec:
|
||||
port: 5217
|
||||
tls:
|
||||
secretName: ttsreader-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose ttsreader-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: ttsreader-web-public
|
||||
# namespace: fc-ttsreader
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`ttsreader.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: ttsreader-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: ttsreader-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -52,9 +52,6 @@ spec:
|
||||
app: updatecenter-web
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/"
|
||||
labels:
|
||||
app: updatecenter-web
|
||||
spec:
|
||||
@@ -66,7 +63,6 @@ spec:
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: http://+:8080
|
||||
|
||||
@@ -12,8 +12,6 @@ All repo-scoped Linux runners use:
|
||||
- `ACCESS_TOKEN` from the `github-runner-token` Secret
|
||||
- `RUN_AS_ROOT=false`
|
||||
- `EPHEMERAL=true`
|
||||
- `DISABLE_AUTO_UPDATE=true` so the runner does not self-update and exit inside
|
||||
the immutable Kubernetes pod
|
||||
- `LABELS=self-hosted,linux,fc-build-linux`
|
||||
- writable non-root paths under `/home/runner` for .NET, NuGet, XDG cache, and
|
||||
Actions tool cache
|
||||
@@ -133,7 +131,3 @@ from GitHub Actions and verify it lands on an `rke2-linux-*` runner.
|
||||
value does not change.
|
||||
- `Multi-Attach` volume error: only the Common runner uses a RWO PVC and it must
|
||||
stay single-replica. New multi-replica runners use `emptyDir`.
|
||||
- Runner pods repeatedly registering, downloading a newer Actions runner, then
|
||||
exiting with code 4: verify `DISABLE_AUTO_UPDATE=true` is present. The image
|
||||
translates that into `config.sh --disableupdate`; without it, the Deployment
|
||||
controller sees the expected self-update exit as CrashLoopBackOff.
|
||||
|
||||
@@ -195,11 +195,6 @@ spec:
|
||||
# fresh registration occurs. Prevents stale runner accumulation.
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
# Labels used by workflow files: runs-on: [self-hosted, linux, fc-build-linux]
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
@@ -371,11 +366,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -514,11 +504,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -651,11 +636,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -788,11 +768,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -925,11 +900,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -1065,11 +1035,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -1202,11 +1167,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -1339,11 +1299,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -1476,11 +1431,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -1615,11 +1565,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -1754,11 +1699,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -1898,11 +1838,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -2037,11 +1972,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -2176,11 +2106,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -2315,11 +2240,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -2453,11 +2373,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -2592,11 +2507,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -2730,11 +2640,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -2868,11 +2773,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3006,11 +2906,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3144,11 +3039,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3282,11 +3172,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3421,11 +3306,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3560,11 +3440,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3699,11 +3574,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3838,11 +3708,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -3977,11 +3842,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -4115,11 +3975,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -4254,11 +4109,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -4397,11 +4247,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -4541,11 +4386,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
@@ -4681,11 +4521,6 @@ spec:
|
||||
value: "/tmp/runner/work"
|
||||
- name: EPHEMERAL
|
||||
value: "true"
|
||||
# The runner image must not self-update inside an immutable
|
||||
# Kubernetes pod. Without this, GitHub runner auto-update exits
|
||||
# with code 4 and the Deployment falls into CrashLoopBackOff.
|
||||
- name: DISABLE_AUTO_UPDATE
|
||||
value: "true"
|
||||
- name: LABELS
|
||||
value: "self-hosted,linux,fc-build-linux"
|
||||
- name: HOME
|
||||
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: intranet-web
|
||||
image: localhost/fc-intranet-web:v20260612-screenshot-metadata
|
||||
image: localhost/fc-intranet-web:v20260531-ttsreader-bridge
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 5300
|
||||
@@ -60,17 +60,14 @@ spec:
|
||||
# ≈ 9 hours. BLUEJAY-WS GPU (R9700, 32GB VRAM) does the same work
|
||||
# in minutes. Memory: feedback_pi5_nomic_embed_slow.
|
||||
- name: IntranetSearch__OllamaBaseUrl
|
||||
value: "http://edge1.iamworkin.lan:11434"
|
||||
# External Notes corpus roots are not mounted in the live pod today.
|
||||
# Keep the curated/workflow docs directory active without logging
|
||||
# repeated /srv/flowercore-notes missing-root warnings.
|
||||
- name: IntranetSearch__Enabled
|
||||
value: "false"
|
||||
# Page-reading override SQLite persistence on the writable PVC at
|
||||
# /data. This backs pronunciation, notes, corrections, and
|
||||
# page-profile metadata across pod restarts.
|
||||
- name: PageReadingOverrides__DatabasePath
|
||||
value: "/data/page-reading-overrides.db"
|
||||
value: "http://10.0.56.20:11434"
|
||||
# Sprint E Phase 2α — JSON-file-backed PageReadingOverride persistence
|
||||
# on the writable PVC at /data. Without this env var the
|
||||
# intranet falls back to the in-memory store (loses state on
|
||||
# pod restart). Master's PageReadingOverrideOptions binds
|
||||
# PageReadingOverrides:FilePath.
|
||||
- name: PageReadingOverrides__FilePath
|
||||
value: "/data/page-reading-overrides.json"
|
||||
- name: KnowledgeFleetSearch__BaseUrl
|
||||
value: "https://knowledge.iamworkin.lan"
|
||||
- name: KnowledgeFleetSearch__ApiKey
|
||||
|
||||
@@ -90,12 +90,9 @@ spec:
|
||||
app.kubernetes.io/name: knowledge-web
|
||||
app.kubernetes.io/part-of: bluejay-infra
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/healthz"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics"
|
||||
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
@@ -119,7 +116,6 @@ spec:
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: "http://+:8080"
|
||||
@@ -127,11 +123,11 @@ spec:
|
||||
value: "Production"
|
||||
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||
value: "false"
|
||||
# AuthentiK/OIDC is enforced. /healthz stays anonymous by contract;
|
||||
# see flowercore.io/healthz-auth-policy above and the Sprint 58
|
||||
# OIDC readiness probe audit.
|
||||
# AuthentiK/OIDC is wired but not enforced until the
|
||||
# knowledge-oidc-client Secret is provisioned and
|
||||
# FlowerCore__Auth__Enabled is flipped to true.
|
||||
- name: FlowerCore__Auth__Enabled
|
||||
value: "true"
|
||||
value: "false"
|
||||
- name: FlowerCore__Auth__Oidc__Enabled
|
||||
value: "true"
|
||||
- name: FlowerCore__Auth__Oidc__Authority
|
||||
@@ -289,26 +285,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: knowledge-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose knowledge-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: knowledge-web-public
|
||||
# namespace: knowledge
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`knowledge.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: knowledge-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: knowledge-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -216,24 +216,19 @@ data:
|
||||
- job_name: "pimanager-app"
|
||||
scrape_interval: 15s
|
||||
metrics_path: /metrics
|
||||
scheme: https
|
||||
tls_config:
|
||||
insecure_skip_verify: true
|
||||
static_configs:
|
||||
- targets: ["piez.iamworkin.lan"]
|
||||
- targets: ["10.0.58.25:5000"]
|
||||
labels:
|
||||
instance: "piez"
|
||||
service: "signalcontrol"
|
||||
service: "pimanager"
|
||||
vlan: "home"
|
||||
device: "pi4-ezconnect"
|
||||
rig: "signal-b"
|
||||
- targets: ["pirelay.iamworkin.lan"]
|
||||
- targets: ["10.0.58.113:5200"]
|
||||
labels:
|
||||
instance: "pirelay"
|
||||
service: "signalcontrol"
|
||||
service: "pimanager"
|
||||
vlan: "home"
|
||||
device: "pi3-ks0212"
|
||||
rig: "signal-a"
|
||||
|
||||
# Epson ET-3750 EcoTank Printer SNMP
|
||||
- job_name: "snmp-printer"
|
||||
@@ -485,34 +480,33 @@ data:
|
||||
- "https://argocd.iamworkin.lan/"
|
||||
- "https://intranet.iamworkin.lan/"
|
||||
- "https://signage.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||
- "https://signalcontrol.iamworkin.lan/health" # FlowerCore.SignalControl explicit health route
|
||||
- "https://kiosk.iamworkin.lan/"
|
||||
- "https://media.iamworkin.lan/healthz" # root auth-gated by OIDC; /healthz anonymous 200
|
||||
- "https://media.iamworkin.lan/"
|
||||
- "https://mysql.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||
- "https://php.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||
- "https://dns.iamworkin.lan/"
|
||||
- "https://zabbix.iamworkin.lan/"
|
||||
- "https://flowercore.iamworkin.lan/healthz"
|
||||
- "https://desktop.iamworkin.lan/"
|
||||
- "https://print.iamworkin.lan/healthz" # root 401 behind API key auth; /healthz anonymous 200
|
||||
- "https://dns.iamworkin.lan/healthz" # root auth-gated by OIDC; /healthz anonymous 200
|
||||
- "https://signalcontrol.iamworkin.lan/health" # FlowerCore.SignalControl Pi control plane
|
||||
- "https://flowercore.iamworkin.lan/healthz" # FlowerCore landing
|
||||
- "https://replay.iamworkin.lan/healthz" # FlowerCore.Signage replay surface
|
||||
- "https://worldbuilder.iamworkin.lan/healthz" # FlowerCore.WorldBuilder
|
||||
- "https://updates.iamworkin.lan/api/v1/manifests/_schema" # UpdateCenter plural LAN alias
|
||||
- "https://updatecenter-internal.iamworkin.lan/api/v1/manifests/_schema" # internal UC schema route
|
||||
- "https://chat.iamworkin.lan/healthz" # OIDC staged; keep blackbox off root before enforcement flips
|
||||
- "https://dist.iamworkin.lan/healthz" # root/admin auth-gated by OIDC; /healthz anonymous 200
|
||||
- "https://dms.iamworkin.lan/healthz" # future OIDC posture; health route is already anonymous/live
|
||||
- "https://print.iamworkin.lan/"
|
||||
- "https://chat.iamworkin.lan/"
|
||||
- "https://dist.iamworkin.lan/"
|
||||
- "https://dms.iamworkin.lan/"
|
||||
- "https://menuboard.iamworkin.lan/"
|
||||
- "https://messageboard.iamworkin.lan/"
|
||||
- "https://presentations.iamworkin.lan/"
|
||||
- "https://retail.iamworkin.lan/"
|
||||
- "https://ttsreader.iamworkin.lan/"
|
||||
- "https://updates.iamworkin.lan/api/v1/manifests/_schema"
|
||||
# Explicit healthcheck paths
|
||||
- "https://library.iamworkin.lan/health"
|
||||
- "https://aistation.iamworkin.lan/healthz"
|
||||
- "https://knowledge.iamworkin.lan/healthz"
|
||||
- "https://fc-llm-bridge.iamworkin.lan/healthz"
|
||||
- "https://acme.iamworkin.lan/health"
|
||||
- "https://replay.iamworkin.lan/healthz"
|
||||
- "https://updatecenter-internal.iamworkin.lan/api/v1/manifests/_schema"
|
||||
- "https://worldbuilder.iamworkin.lan/healthz"
|
||||
# Coverage gaps logged Q-MR-129/Q-MR-130: devices.iamworkin.lan
|
||||
# returns 503 and e2e-test-pma/wpdemo only return 404.
|
||||
# NOTE: services intentionally NOT in this probe surface
|
||||
# - grafana.iamworkin.lan: every endpoint (incl. /api/health
|
||||
# and /login) returns 401 behind Traefik basic-auth.
|
||||
@@ -921,14 +915,16 @@ data:
|
||||
# for: 30m absorbs sleep cycles. The EcoTank sleeps after ~5 min
|
||||
# of idle and SNMP times out, so 5m for: would page nightly. A
|
||||
# genuine printer outage (jam, disconnected) lasts well over 30m.
|
||||
# Use a range-window expression: instant up{} can go stale/absent
|
||||
# after repeated snmp-exporter 500s.
|
||||
- alert: EpsonPrinterDown
|
||||
expr: (max_over_time(up{job="snmp-printer"}[35m]) == bool 0) == 1 and (hour() >= 13 or hour() < 1)
|
||||
for: 30m
|
||||
labels:
|
||||
severity: info
|
||||
severity: warning
|
||||
alert_channel: irc
|
||||
annotations:
|
||||
summary: "Epson ET-3750 SNMP unreachable during waking hours (30m)"
|
||||
summary: "Epson ET-3750 SNMP unreachable for >30m (likely actual fault, not sleep)"
|
||||
|
||||
- alert: SynologyDiskLow
|
||||
expr: hrStorageUsed{job="snmp-nas"} / hrStorageSize{job="snmp-nas"} * 100 > 85
|
||||
@@ -1036,11 +1032,8 @@ data:
|
||||
rules:
|
||||
- alert: KubeContainerRestartingFrequently
|
||||
# Exclude github-runner: ephemeral runners register, run one job,
|
||||
# exit cleanly, and restart by design. Also require kube_pod_info so
|
||||
# deleted rollout pods do not keep firing from retained restart series.
|
||||
expr: |
|
||||
increase(kube_pod_container_status_restarts_total{namespace!="github-runner"}[1h]) > 5
|
||||
and on(namespace, pod) kube_pod_info
|
||||
# exit cleanly, then restart by design.
|
||||
expr: increase(kube_pod_container_status_restarts_total{namespace!="github-runner"}[1h]) > 5
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
@@ -1049,12 +1042,9 @@ data:
|
||||
description: "Container {{ $labels.container }} in pod {{ $labels.namespace }}/{{ $labels.pod }} has restarted {{ $value | printf \"%.0f\" }} times in the last hour. Check 'kubectl describe pod' + last-state termination reason."
|
||||
|
||||
- alert: KubeContainerCrashLooping
|
||||
# Same github-runner/delete-retention exclusions as the hourly
|
||||
# restart rule above; real runner failures are covered by the
|
||||
# dedicated LinuxRunnerOffline/MacMiniRunnerOffline alerts.
|
||||
expr: |
|
||||
increase(kube_pod_container_status_restarts_total{namespace!="github-runner"}[15m]) > 3
|
||||
and on(namespace, pod) kube_pod_info
|
||||
# Exclude github-runner: ephemeral runners register, run one job,
|
||||
# exit cleanly, then restart by design.
|
||||
expr: increase(kube_pod_container_status_restarts_total{namespace!="github-runner"}[15m]) > 3
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
@@ -1082,9 +1072,7 @@ data:
|
||||
description: "Pod can't pull image. Check the image ref (often a stale tag or unreachable registry) and clean up if it's an orphan."
|
||||
|
||||
- alert: KubeDeploymentReplicasMismatch
|
||||
# github-runner has explicit runner-offline alerts; the generic
|
||||
# replica-mismatch rule should not page on intentionally ephemeral
|
||||
# 0/1 runner churn between CI jobs.
|
||||
# Exclude github-runner: ephemeral runner deployments flap 0/1 between jobs by design.
|
||||
expr: kube_deployment_spec_replicas{namespace!="github-runner"} != kube_deployment_status_replicas_available{namespace!="github-runner"}
|
||||
for: 15m
|
||||
labels:
|
||||
@@ -3664,6 +3652,38 @@ data:
|
||||
relativeTimeRange: {from: 120, to: 0}
|
||||
datasourceUid: __expr__
|
||||
model: {type: threshold, expression: B, conditions: [{evaluator: {params: [600], type: gt}}], refId: C}
|
||||
- orgId: 1
|
||||
name: SNMP Devices
|
||||
folder: Infrastructure Alerts
|
||||
interval: 1m
|
||||
rules:
|
||||
- uid: epson-printer-down-stale-window
|
||||
title: EpsonPrinterDown
|
||||
condition: C
|
||||
for: 30m
|
||||
noDataState: OK
|
||||
execErrState: OK
|
||||
annotations:
|
||||
summary: Epson ET-3750 SNMP unreachable
|
||||
description: The Epson ET-3750 snmp-printer target has reported only failed scrapes for at least 35 minutes.
|
||||
runbook: "1. Check if printer is intentionally powered off 2. If printing needed: press power button on printer 3. Ping 10.0.58.107 after wake-up 4. Check WiFi on printer LCD if still unreachable"
|
||||
labels:
|
||||
severity: info
|
||||
service: printer
|
||||
alert_channel: irc
|
||||
data:
|
||||
- refId: A
|
||||
relativeTimeRange: {from: 2100, to: 0}
|
||||
datasourceUid: prometheus
|
||||
model: {expr: '(max_over_time(up{job="snmp-printer"}[35m]) == bool 0) == 1 and (hour() >= 13 or hour() < 1)', instant: true, refId: A}
|
||||
- refId: B
|
||||
relativeTimeRange: {from: 2100, to: 0}
|
||||
datasourceUid: __expr__
|
||||
model: {type: reduce, expression: A, reducer: last, refId: B}
|
||||
- refId: C
|
||||
relativeTimeRange: {from: 2100, to: 0}
|
||||
datasourceUid: __expr__
|
||||
model: {type: threshold, expression: B, conditions: [{evaluator: {params: [0], type: gt}}], refId: C}
|
||||
- orgId: 1
|
||||
name: CI Runners
|
||||
folder: CI Alerts
|
||||
|
||||
@@ -114,9 +114,6 @@ spec:
|
||||
app: telephony-web
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/health"
|
||||
labels:
|
||||
app: telephony-web
|
||||
spec:
|
||||
@@ -164,7 +161,6 @@ spec:
|
||||
ports:
|
||||
- containerPort: 5100
|
||||
name: http
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
env:
|
||||
- name: Telephony__Twilio__AccountSid
|
||||
valueFrom:
|
||||
@@ -391,3 +387,4 @@ spec:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,27 +12,28 @@ Source: `D:\git\FlowerCore\FlowerCore.WorldBuilder` (master)
|
||||
in pfSense Unbound before this manifest is applied, or cert-manager
|
||||
HTTP-01 silently exponential-backs-off ~2h.
|
||||
Memory: `feedback_pfsense_dns_required_for_acme`.
|
||||
2. **Image import to ALL Ready RKE2 nodes** — pod can currently schedule to
|
||||
`rke2-server` (10.0.56.11) and `rke2-agent1` (10.0.56.12). Build with:
|
||||
2. **Image import to ALL RKE2 nodes** — pod can schedule to any of
|
||||
`rke2-server` (10.0.56.11), `rke2-agent1` (10.0.56.12),
|
||||
`rke2-agent2` (10.0.56.13). Build with:
|
||||
```bash
|
||||
bash deploy/build.sh # in FlowerCore.WorldBuilder repo
|
||||
mkdir -p artifacts/deploy
|
||||
podman save localhost/fc-worldbuilder:v<TAG> -o artifacts/deploy/fc-worldbuilder-v<TAG>.tar
|
||||
for h in 10.0.56.11 10.0.56.12; do
|
||||
ssh fcadmin@$h "mkdir -p /home/fcadmin/.fcv"
|
||||
scp artifacts/deploy/fc-worldbuilder-v<TAG>.tar fcadmin@$h:/home/fcadmin/.fcv/
|
||||
podman save localhost/fc-worldbuilder:v<TAG> -o /tmp/fc-worldbuilder-v<TAG>.tar
|
||||
for h in 10.0.56.11 10.0.56.12 10.0.56.13; do
|
||||
scp /tmp/fc-worldbuilder-v<TAG>.tar fcadmin@$h:/tmp/
|
||||
ssh fcadmin@$h \
|
||||
"sudo /var/lib/rancher/rke2/bin/ctr -a /run/k3s/containerd/containerd.sock \
|
||||
-n k8s.io images import /home/fcadmin/.fcv/fc-worldbuilder-v<TAG>.tar"
|
||||
-n k8s.io images import /tmp/fc-worldbuilder-v<TAG>.tar"
|
||||
done
|
||||
```
|
||||
Memory: `feedback_rke2_image_import_per_node_scp`.
|
||||
3. **Bump image tag** in `worldbuilder.yaml` and git push.
|
||||
ArgoCD ApplicationSet picks up within ~3 minutes.
|
||||
4. **First production render** — verify
|
||||
`https://worldbuilder.iamworkin.lan/healthz`, open
|
||||
`https://worldbuilder.iamworkin.lan/settings`, and confirm the image backend
|
||||
reports ComfyUI before running an operator-owned render lane.
|
||||
4. **First production render** — open
|
||||
`https://worldbuilder.iamworkin.lan/studio/c32e0000-0000-4000-8000-000000000004`
|
||||
and confirm the Cyberpunk Blue Jay demo prompt loads with five seeded fake
|
||||
generated images. This Sprint 32 visitor-safe profile uses
|
||||
`ClientMode=fake`; switch the image-generation env vars back to ComfyUI only
|
||||
for an operator-owned GPU render lane.
|
||||
|
||||
## Health probes
|
||||
|
||||
@@ -55,8 +56,13 @@ Source: `D:\git\FlowerCore\FlowerCore.WorldBuilder` (master)
|
||||
|
||||
## Image generation backend
|
||||
|
||||
The live internal profile now uses
|
||||
`FlowerCore:WorldBuilder:ImageGeneration:ClientMode=comfyui` with
|
||||
`BaseUrl=http://10.0.56.20:8188` on BLUEJAY-WS (R9700 / gfx1201 / ROCm 7.2).
|
||||
Keep the public host pre-staging disabled unless the five safe-to-expose gates
|
||||
are rechecked; the live GPU lane is operator-owned and internal-only.
|
||||
Sprint 32 pins the Kubernetes profile to
|
||||
`FlowerCore:WorldBuilder:ImageGeneration:ClientMode=fake` with
|
||||
`BaseUrl=http://127.0.0.1:1`. That keeps the public/internal visitor demo
|
||||
deterministic, avoids GPU exposure, and still exercises the studio/gallery
|
||||
surface with persisted generated-image metadata.
|
||||
|
||||
The previous ComfyUI backend target was `http://10.0.56.20:8188` on
|
||||
BLUEJAY-WS (R9700 / gfx1201 / ROCm 7.2.1). Re-enable it only in an
|
||||
operator-owned follow-up that also verifies workstation reachability and image
|
||||
import freshness.
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#
|
||||
# Image build (BLUEJAY-WS):
|
||||
# bash deploy/build.sh # in FlowerCore.WorldBuilder repo
|
||||
# podman save localhost/fc-worldbuilder:v<TAG> -o artifacts/deploy/fc-worldbuilder-v<TAG>.tar
|
||||
# for h in 10.0.56.11 10.0.56.12; do
|
||||
# scp artifacts/deploy/fc-worldbuilder-v<TAG>.tar fcadmin@$h:/home/fcadmin/.fcv/
|
||||
# ssh fcadmin@$h "sudo /var/lib/rancher/rke2/bin/ctr -a /run/k3s/containerd/containerd.sock -n k8s.io images import /home/fcadmin/.fcv/fc-worldbuilder-v<TAG>.tar"
|
||||
# podman save localhost/fc-worldbuilder:v<TAG> -o /tmp/fc-worldbuilder-v<TAG>.tar
|
||||
# for h in 10.0.56.11 10.0.56.12 10.0.56.13; do
|
||||
# scp /tmp/fc-worldbuilder-v<TAG>.tar fcadmin@$h:/tmp/
|
||||
# ssh fcadmin@$h "sudo /var/lib/rancher/rke2/bin/ctr -a /run/k3s/containerd/containerd.sock -n k8s.io images import /tmp/fc-worldbuilder-v<TAG>.tar"
|
||||
# done
|
||||
---
|
||||
apiVersion: v1
|
||||
@@ -77,8 +77,6 @@ spec:
|
||||
flowercore.io/tenant-id: system
|
||||
flowercore.io/created-by: bluejay-infra
|
||||
annotations:
|
||||
fc.flowercore.io/healthz-anon: "true"
|
||||
fc.flowercore.io/probe-path: "/healthz"
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8080"
|
||||
prometheus.io/path: "/metrics/prometheus"
|
||||
@@ -90,12 +88,11 @@ spec:
|
||||
containers:
|
||||
- name: web
|
||||
# Bump tag for each rebuild. Initial deploy: v202605062048
|
||||
image: localhost/fc-worldbuilder:v202606121657-35aaa2c-gpu
|
||||
image: localhost/fc-worldbuilder:v202605062048
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||
env:
|
||||
- name: ASPNETCORE_URLS
|
||||
value: "http://+:8080"
|
||||
@@ -117,16 +114,14 @@ spec:
|
||||
value: "/data/gallery"
|
||||
- name: FlowerCore__WorldBuilder__Export__RootPath
|
||||
value: "/data/exports"
|
||||
# Operator-approved live GPU lane. Internal-only host targets
|
||||
# BLUEJAY-WS ComfyUI; keep public host pre-staging disabled below.
|
||||
# Visitor-safe Sprint 32 profile: fake backend keeps public demo
|
||||
# rendering deterministic and avoids exposing BLUEJAY-WS GPU.
|
||||
- name: FlowerCore__WorldBuilder__ImageGeneration__BaseUrl
|
||||
value: "http://10.0.56.20:8188"
|
||||
value: "http://127.0.0.1:1"
|
||||
- name: FlowerCore__WorldBuilder__ImageGeneration__ClientMode
|
||||
value: "comfyui"
|
||||
value: "fake"
|
||||
- name: FlowerCore__WorldBuilder__ImageGeneration__BackendId
|
||||
value: "comfyui"
|
||||
- name: FlowerCore__WorldBuilder__ImageGeneration__VisitorSafe
|
||||
value: "false"
|
||||
value: "fake"
|
||||
resources:
|
||||
# Cluster CPU-request budget runs hot (99% on all 3 nodes at deploy
|
||||
# time) while actual CPU usage is well below capacity. Idle Blazor
|
||||
@@ -259,26 +254,3 @@ spec:
|
||||
port: 80
|
||||
tls:
|
||||
secretName: worldbuilder-web-tls
|
||||
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||
# When the operator decides to expose worldbuilder-web publicly, uncomment + update the host,
|
||||
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||
#
|
||||
# --- IngressRoute ---
|
||||
# apiVersion: traefik.io/v1alpha1
|
||||
# kind: IngressRoute
|
||||
# metadata:
|
||||
# name: worldbuilder-web-public
|
||||
# namespace: worldbuilder
|
||||
# spec:
|
||||
# entryPoints: [websecure]
|
||||
# routes:
|
||||
# - match: Host(`worldbuilder.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||
# kind: Rule
|
||||
# middlewares:
|
||||
# - name: worldbuilder-web-public-profile-header # injects entitlement profile
|
||||
# services:
|
||||
# - name: worldbuilder-web
|
||||
# port: 80
|
||||
# tls: {}
|
||||
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||
|
||||
@@ -15,19 +15,24 @@ public sealed class FleetManifestLintTests
|
||||
{
|
||||
"brochure.flowercore.io",
|
||||
"dist.flowercore.io",
|
||||
"dns.iamworkin.lan",
|
||||
};
|
||||
|
||||
// Hosts that allow a tightly bounded write surface in addition to GET/HEAD.
|
||||
// updatecenter.iamworkin.lan accepts POST /api/v1/checkin/{id}
|
||||
// Public hosts that allow a tightly bounded write surface in addition to
|
||||
// GET/HEAD. updatecenter.iamworkin.lan accepts POST /api/v1/checkin/{id}
|
||||
// (bootstrap-JWT) so its allowlist is GET||HEAD||POST||OPTIONS — but
|
||||
// PUT/PATCH/DELETE must still 404 at the route. Public
|
||||
// update.flowercore.io remains a GET/HEAD download surface in the
|
||||
// FlowerCore.Updater sibling manifest and is covered by the general
|
||||
// public-method allowlist lint instead of this write-surface rule.
|
||||
// PUT/PATCH/DELETE must still 404 at the route. Anything wider than this
|
||||
// set should fail this lint.
|
||||
//
|
||||
// PUB-1 (2026-05-06): update.flowercore.io / updates.flowercore.io were
|
||||
// added for the Cloudflare-proxied public Update Center edge. They use the
|
||||
// same bounded read-write allowlist as the LAN pair.
|
||||
private static readonly HashSet<string> PublicReadWriteAllowlistHosts = new(StringComparer.Ordinal)
|
||||
{
|
||||
"updatecenter.iamworkin.lan",
|
||||
"updates.iamworkin.lan",
|
||||
"update.flowercore.io",
|
||||
"updates.flowercore.io",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> ApiKeyProtectedDeployments = new(StringComparer.Ordinal)
|
||||
@@ -65,7 +70,7 @@ public sealed class FleetManifestLintTests
|
||||
["github-runner-updater"] = "https://github.com/astoltz/FlowerCore.Updater",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> RepoScopedLinuxRunnerDeployments = new(StringComparer.Ordinal)
|
||||
private static readonly HashSet<string> ScaledLinuxRunnerDeployments = new(StringComparer.Ordinal)
|
||||
{
|
||||
"github-runner-sharedpos",
|
||||
"github-runner-puppet",
|
||||
@@ -79,44 +84,6 @@ public sealed class FleetManifestLintTests
|
||||
"github-runner-updater",
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, (string Deployment, string ProbePath)> BroaderHardeningDeployments =
|
||||
new Dictionary<string, (string Deployment, string ProbePath)>(StringComparer.Ordinal)
|
||||
{
|
||||
["fc-aistation"] = ("aistation-web", "/healthz"),
|
||||
["fc-chat"] = ("chat-web", "/healthz"),
|
||||
["fc-devicemgmt"] = ("fc-devicemgmt-web", "/healthz"),
|
||||
["fc-library"] = ("library-web", "/health"),
|
||||
["fc-llm-bridge"] = ("fc-llm-bridge", "/healthz"),
|
||||
["fc-messageboard"] = ("messageboard-web", "/health"),
|
||||
["fc-retail"] = ("retail-web", "/healthz"),
|
||||
["fc-ttsreader"] = ("ttsreader-web", "/health"),
|
||||
["fc-updater"] = ("updatecenter-web", "/"),
|
||||
["knowledge"] = ("knowledge-web", "/healthz"),
|
||||
["telephony"] = ("telephony-web", "/health"),
|
||||
["worldbuilder"] = ("worldbuilder-web", "/healthz"),
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> BroaderHardeningInternalPrestageApps = new(StringComparer.Ordinal)
|
||||
{
|
||||
"fc-aistation",
|
||||
"fc-desktop",
|
||||
"fc-dms",
|
||||
"fc-library",
|
||||
"fc-llm-bridge",
|
||||
"fc-menuboard",
|
||||
"fc-messageboard",
|
||||
"fc-mysql",
|
||||
"fc-php",
|
||||
"fc-presentations",
|
||||
"fc-retail",
|
||||
"fc-scoreboard",
|
||||
"fc-segmentdisplay",
|
||||
"fc-signage",
|
||||
"fc-ttsreader",
|
||||
"knowledge",
|
||||
"worldbuilder",
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, string> WritableRunnerEnv = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["HOME"] = "/home/runner",
|
||||
@@ -272,7 +239,6 @@ public sealed class FleetManifestLintTests
|
||||
var container = deployments[expectedRunner.Key].MainContainerMappings().Should().ContainSingle().Subject;
|
||||
EnvValue(container, "REPO_URL").Should().Be(expectedRunner.Value);
|
||||
EnvValue(container, "EPHEMERAL").Should().Be("true");
|
||||
EnvValue(container, "DISABLE_AUTO_UPDATE").Should().Be("true", $"{expectedRunner.Key} must not self-update inside immutable Kubernetes runner pods");
|
||||
EnvValue(container, "LABELS").Should().Be("self-hosted,linux,fc-build-linux");
|
||||
EnvValue(container, "RUN_AS_ROOT").Should().Be("false");
|
||||
EnvValue(container, "ACCESS_TOKEN").Should().BeNull("ACCESS_TOKEN must come from github-runner-token Secret, not a literal");
|
||||
@@ -306,17 +272,17 @@ public sealed class FleetManifestLintTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GitHubRunnerFleet_MustAvoidRwoMultiAttachForRepoScopedDeployments()
|
||||
public void GitHubRunnerFleet_MustAvoidRwoMultiAttachForScaledDeployments()
|
||||
{
|
||||
var deployments = GitHubRunnerDeployments();
|
||||
|
||||
foreach (var deploymentName in RepoScopedLinuxRunnerDeployments)
|
||||
foreach (var deploymentName in ScaledLinuxRunnerDeployments)
|
||||
{
|
||||
var deployment = deployments[deploymentName];
|
||||
// Sprint 34 ops trimmed runner load while the cluster was degraded
|
||||
// to two healthy nodes. Repo-scoped runners can be tuned back above
|
||||
// one replica, but they must stay RWO-safe before that happens.
|
||||
ReplicaCount(deployment).Should().BeGreaterOrEqualTo(1, $"{deploymentName} must keep at least one repo-scoped runner online");
|
||||
// Scaled runners must have >= 2 replicas (avoid single-pod bottleneck).
|
||||
// Individual deployments may be tuned upward per CI activity — see
|
||||
// "runners: right-size replica counts per 14d CI activity (#24)".
|
||||
ReplicaCount(deployment).Should().BeGreaterOrEqualTo(2, $"{deploymentName} is in the scaled set and must run with at least 2 replicas");
|
||||
|
||||
var volumes = deployment.MappingSequence("spec", "template", "spec", "volumes");
|
||||
var claimNames = volumes
|
||||
@@ -324,7 +290,7 @@ public sealed class FleetManifestLintTests
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||
.ToList();
|
||||
|
||||
claimNames.Should().BeEmpty($"{deploymentName} must remain ready for safe multi-replica scaling without sharing a RWO PVC");
|
||||
claimNames.Should().BeEmpty($"{deploymentName} is scaled and must not share a RWO PVC");
|
||||
volumes.Should().Contain(volume =>
|
||||
string.Equals(ManifestNodeExtensions.Scalar(volume, "name"), "nuget-cache", StringComparison.Ordinal)
|
||||
&& ManifestNodeExtensions.Mapping(volume, "emptyDir") != null);
|
||||
@@ -458,82 +424,6 @@ public sealed class FleetManifestLintTests
|
||||
monitoring.Should().Contain("alert_channel: irc");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Monitoring_GenericKubernetesAlerts_MustExcludeEphemeralGithubRunnerNamespace()
|
||||
{
|
||||
var monitoring = File.ReadAllText(Path.Combine(Inventory.BluejayRoot, "apps", "monitoring", "noc-monitoring.yaml"));
|
||||
|
||||
monitoring.Should().Contain("kube_pod_container_status_restarts_total{namespace!=\"github-runner\"}");
|
||||
monitoring.Should().Contain("and on(namespace, pod) kube_pod_info");
|
||||
monitoring.Should().Contain("kube_deployment_spec_replicas{namespace!=\"github-runner\"} != kube_deployment_status_replicas_available{namespace!=\"github-runner\"}");
|
||||
monitoring.Should().Contain("dedicated LinuxRunnerOffline/MacMiniRunnerOffline alerts");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Monitoring_BlackboxTargetsForOidcSensitiveServices_MustUseAnonymousHealthRoutesWhenAvailable()
|
||||
{
|
||||
var monitoring = File.ReadAllText(Path.Combine(Inventory.BluejayRoot, "apps", "monitoring", "noc-monitoring.yaml"));
|
||||
|
||||
monitoring.Should().Contain("https://chat.iamworkin.lan/healthz");
|
||||
monitoring.Should().Contain("https://dist.iamworkin.lan/healthz");
|
||||
monitoring.Should().Contain("https://dms.iamworkin.lan/healthz");
|
||||
monitoring.Should().Contain("https://print.iamworkin.lan/healthz");
|
||||
monitoring.Should().Contain("https://knowledge.iamworkin.lan/healthz");
|
||||
monitoring.Should().Contain("https://library.iamworkin.lan/health");
|
||||
monitoring.Should().Contain("https://aistation.iamworkin.lan/healthz");
|
||||
monitoring.Should().NotContain("https://print.iamworkin.lan/\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcEnforcedDeployments_WithHttpHealthzProbes_MustDeclareAnonymousHealthzContract()
|
||||
{
|
||||
var violations = Inventory.Documents
|
||||
.Where(document => document.Kind == "Deployment")
|
||||
.SelectMany(document => document.MainContainerMappings()
|
||||
.Where(container => string.Equals(EnvValue(container, "FlowerCore__Auth__Enabled"), "true", StringComparison.OrdinalIgnoreCase))
|
||||
.Where(container => string.Equals(EnvValue(container, "FlowerCore__Auth__Oidc__Enabled"), "true", StringComparison.OrdinalIgnoreCase))
|
||||
.Where(container => ProbeHttpGetPath(container, "readinessProbe") == "/healthz"
|
||||
|| ProbeHttpGetPath(container, "startupProbe") == "/healthz")
|
||||
.Where(_ => !string.Equals(
|
||||
PodAnnotation(document, "flowercore.io/healthz-auth-policy"),
|
||||
"allow-anonymous",
|
||||
StringComparison.Ordinal))
|
||||
.Select(container =>
|
||||
{
|
||||
var containerName = ManifestNodeExtensions.Scalar(container, "name") ?? "<unnamed>";
|
||||
return $"{document.Descriptor} container '{containerName}' enforces OIDC while probing /healthz but lacks flowercore.io/healthz-auth-policy: allow-anonymous.";
|
||||
}))
|
||||
.ToList();
|
||||
|
||||
violations.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Knowledge_OidcEnforcement_MustKeepHealthzAnonymousContractVisibleInManifest()
|
||||
{
|
||||
var knowledge = Inventory.Documents
|
||||
.Single(document => document.Kind == "Deployment" && document.Namespace == "knowledge" && document.Name == "knowledge-web");
|
||||
var container = knowledge.MainContainerMappings().Should().ContainSingle().Subject;
|
||||
|
||||
EnvValue(container, "FlowerCore__Auth__Enabled").Should().Be("true");
|
||||
EnvValue(container, "FlowerCore__Auth__Oidc__Enabled").Should().Be("true");
|
||||
ProbeHttpGetPath(container, "readinessProbe").Should().Be("/healthz");
|
||||
PodAnnotation(knowledge, "flowercore.io/healthz-auth-policy").Should().Be("allow-anonymous");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Distribution_OidcEnforcement_MustKeepHealthzAnonymousContractVisibleInManifest()
|
||||
{
|
||||
var distribution = Inventory.Documents
|
||||
.Single(document => document.Kind == "Deployment" && document.Namespace == "fc-distribution" && document.Name == "fc-distribution");
|
||||
var container = distribution.MainContainerMappings().Should().ContainSingle().Subject;
|
||||
|
||||
EnvValue(container, "FlowerCore__Auth__Oidc__Enabled").Should().Be("true");
|
||||
EnvValue(container, "FlowerCore__Auth__Enabled").Should().Be("true");
|
||||
ProbeHttpGetPath(container, "readinessProbe").Should().Be("/healthz");
|
||||
PodAnnotation(distribution, "flowercore.io/healthz-auth-policy").Should().Be("allow-anonymous");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StatefulSets_WithVolumeClaimTemplates_MustDeclareFilesystemDefaults()
|
||||
{
|
||||
@@ -647,10 +537,10 @@ public sealed class FleetManifestLintTests
|
||||
var expectedFiles = new[]
|
||||
{
|
||||
"1password-item.yaml",
|
||||
"argocd-application.yaml",
|
||||
"certificate-web.yaml",
|
||||
"clusterrole-operator.yaml",
|
||||
"clusterrolebinding-operator.yaml",
|
||||
"crds.yaml",
|
||||
"deployment-operator.yaml",
|
||||
"deployment-web.yaml",
|
||||
"ingressroute-web.yaml",
|
||||
@@ -740,8 +630,7 @@ public sealed class FleetManifestLintTests
|
||||
.Single(document => document.Kind == "ClusterRole" && document.Name == "fc-devicemgmt-operator");
|
||||
var allScalars = clusterRole.AllScalars().ToList();
|
||||
|
||||
allScalars.Should().Contain("flowercore.io");
|
||||
allScalars.Should().NotContain("devices.flowercore.io");
|
||||
allScalars.Should().Contain("devices.flowercore.io");
|
||||
allScalars.Should().Contain("*");
|
||||
allScalars.Should().Contain("deployments");
|
||||
allScalars.Should().Contain("get");
|
||||
@@ -770,7 +659,7 @@ public sealed class FleetManifestLintTests
|
||||
|
||||
FcDeviceManagementDocuments().Should().NotContain(document => document.Kind == "Secret");
|
||||
appText.Should().Contain("secretKeyRef:");
|
||||
appText.Should().Contain("name: fc-devicemgmt-runtime");
|
||||
appText.Should().Contain("secretName: fc-devicemgmt-runtime");
|
||||
appText.Should().NotContain("stringData:");
|
||||
appText.Should().NotContain("from-literal");
|
||||
appText.Should().NotContain("tls.key:");
|
||||
@@ -804,202 +693,17 @@ public sealed class FleetManifestLintTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FcDeviceManagement_MustRelyOnApplicationSetDiscovery()
|
||||
public void FcDeviceManagement_ArgocdApplicationMustMatchApplicationSetDiscoveryConventions()
|
||||
{
|
||||
var documents = FcDeviceManagementDocuments();
|
||||
var application = FcDeviceManagementDocuments()
|
||||
.Single(document => document.Kind == "Application" && document.Name == "infra-fc-devicemgmt");
|
||||
|
||||
documents.Should().NotContain(document => document.Kind == "Application");
|
||||
|
||||
var ns = documents.Single(document => document.Kind == "Namespace" && document.Name == "fc-devicemgmt");
|
||||
ns.FileText.Should().Contain("ArgoCD discovers this directory as Application `infra-fc-devicemgmt`.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BroaderHardeningDeployments_MustAnnotateAnonymousHealthProbeIntent()
|
||||
{
|
||||
foreach (var expected in BroaderHardeningDeployments)
|
||||
{
|
||||
var deployment = AppDocuments(expected.Key)
|
||||
.Single(document => document.Kind == "Deployment" && document.Name == expected.Value.Deployment);
|
||||
|
||||
PodAnnotation(deployment, "fc.flowercore.io/healthz-anon").Should().Be("true");
|
||||
PodAnnotation(deployment, "fc.flowercore.io/probe-path").Should().Be(expected.Value.ProbePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BroaderHardeningDeployments_MustDocumentForwardedProtoAuthPosture()
|
||||
{
|
||||
foreach (var expected in BroaderHardeningDeployments)
|
||||
{
|
||||
var deployment = AppDocuments(expected.Key)
|
||||
.Single(document => document.Kind == "Deployment" && document.Name == expected.Value.Deployment);
|
||||
|
||||
deployment.FileText.Should().Contain(
|
||||
"fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178)");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BroaderHardeningInternalApps_MustOnlyPrestageCommentedPublicMethodAllowlist()
|
||||
{
|
||||
foreach (var app in BroaderHardeningInternalPrestageApps)
|
||||
{
|
||||
var documents = AppDocuments(app);
|
||||
var text = string.Join(Environment.NewLine, documents.Select(document => document.FileText));
|
||||
|
||||
text.Should().Contain("PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only)");
|
||||
text.Should().Contain("# - match: Host(`");
|
||||
text.Should().Contain("Method(`GET`) || Method(`HEAD`)");
|
||||
|
||||
documents
|
||||
.Where(document => document.Kind == "IngressRoute")
|
||||
.SelectMany(document => document.MappingSequence("spec", "routes"))
|
||||
.Select(route => ManifestNodeExtensions.Scalar(route, "match") ?? string.Empty)
|
||||
application.Namespace.Should().Be("argocd");
|
||||
application.Scalar("spec", "source", "repoURL")
|
||||
.Should()
|
||||
.NotContain(match => match.Contains(".flowercore.io", StringComparison.Ordinal),
|
||||
"Sprint 61 broader hardening only pre-stages commented public hosts for internal-only apps");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcFlipServices_AreGitOpsManagedWithHealthzProbes()
|
||||
{
|
||||
var deployments = new[]
|
||||
{
|
||||
(App: "fc-dns", Name: "dns-web", Slug: "dns", Secret: "dns-oidc-client", AuthEnabled: "false"),
|
||||
(App: "fc-media", Name: "fc-media-web", Slug: "media", Secret: "media-oidc-client", AuthEnabled: "true"),
|
||||
(App: "fc-distribution", Name: "fc-distribution", Slug: "distribution", Secret: "distribution-oidc-client", AuthEnabled: "true"),
|
||||
};
|
||||
|
||||
foreach (var expected in deployments)
|
||||
{
|
||||
var deployment = AppDocuments(expected.App)
|
||||
.Single(document => document.Kind == "Deployment" && document.Name == expected.Name);
|
||||
var container = deployment.MainContainerMappings().Should().ContainSingle().Subject;
|
||||
|
||||
EnvValue(container, "FlowerCore__Auth__Enabled").Should().Be(expected.AuthEnabled);
|
||||
EnvValue(container, "FlowerCore__Auth__Oidc__Enabled").Should().Be("true");
|
||||
(EnvValue(container, "FlowerCore__Auth__Oidc__Audience") ?? EnvValue(container, "FlowerCore__Auth__Oidc__ClientId"))
|
||||
.Should()
|
||||
.Be(expected.Slug);
|
||||
EnvSecretName(container, "FlowerCore__Auth__Oidc__ClientSecret").Should().Be(expected.Secret);
|
||||
EnvSecretOptional(container, "FlowerCore__Auth__Oidc__ClientSecret").Should().Be("true");
|
||||
|
||||
ProbePath(container, "readinessProbe").Should().Be("/healthz");
|
||||
if (ProbePath(container, "startupProbe") is { } startupProbePath)
|
||||
{
|
||||
startupProbePath.Should().Be("/healthz");
|
||||
}
|
||||
|
||||
if (ProbePath(container, "livenessProbe") is { } livenessProbePath)
|
||||
{
|
||||
livenessProbePath.Should().Be("/healthz");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcFlipServices_UseOnePasswordItemClientSecrets()
|
||||
{
|
||||
var expectedItems = new Dictionary<string, (string Name, string ItemPath)>(StringComparer.Ordinal)
|
||||
{
|
||||
["fc-dns"] = ("dns-oidc-client", "vaults/IAmWorkin/items/dns-oidc-client"),
|
||||
["fc-media"] = ("media-oidc-client", "vaults/IAmWorkin/items/media-oidc-client"),
|
||||
["fc-distribution"] = ("distribution-oidc-client", "vaults/IAmWorkin/items/distribution-oidc-client"),
|
||||
};
|
||||
|
||||
foreach (var expected in expectedItems)
|
||||
{
|
||||
var item = AppDocuments(expected.Key)
|
||||
.Single(document => document.Kind == "OnePasswordItem" && document.Name == expected.Value.Name);
|
||||
|
||||
item.Scalar("spec", "itemPath").Should().Be(expected.Value.ItemPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DnsAndMediaGitOpsAdoption_PreservesLiveStorageAndImageShape()
|
||||
{
|
||||
var dnsDeployment = AppDocuments("fc-dns")
|
||||
.Single(document => document.Kind == "Deployment" && document.Name == "dns-web");
|
||||
var dnsContainer = dnsDeployment.MainContainerMappings().Should().ContainSingle().Subject;
|
||||
var dnsPvc = AppDocuments("fc-dns")
|
||||
.Single(document => document.Kind == "PersistentVolumeClaim" && document.Name == "dns-web-data");
|
||||
|
||||
ManifestNodeExtensions.Scalar(dnsContainer, "image").Should().Be("localhost/fc-dns-web:v20260612-l4dns-a5d2849");
|
||||
dnsPvc.Scalar("spec", "storageClassName").Should().Be("longhorn");
|
||||
dnsPvc.Scalar("spec", "resources", "requests", "storage").Should().Be("1Gi");
|
||||
|
||||
var mediaDeployment = AppDocuments("fc-media")
|
||||
.Single(document => document.Kind == "Deployment" && document.Name == "fc-media-web");
|
||||
var mediaContainer = mediaDeployment.MainContainerMappings().Should().ContainSingle().Subject;
|
||||
var mediaPvc = AppDocuments("fc-media")
|
||||
.Single(document => document.Kind == "PersistentVolumeClaim" && document.Name == "fc-media-data");
|
||||
|
||||
ManifestNodeExtensions.Scalar(mediaContainer, "image").Should().Be("localhost/fc-media-web:v20260604-oidc-proper");
|
||||
mediaPvc.Scalar("spec", "storageClassName").Should().Be("longhorn");
|
||||
mediaPvc.Scalar("spec", "resources", "requests", "storage").Should().Be("20Gi");
|
||||
|
||||
mediaDeployment.AllScalars().Should().Contain(new[]
|
||||
{
|
||||
"/volume1/kubernetes/fc-media-transcodes",
|
||||
"/volume1/kubernetes/fc-media-inbox",
|
||||
"/volume1/video",
|
||||
});
|
||||
|
||||
var distributionDeployment = AppDocuments("fc-distribution")
|
||||
.Single(document => document.Kind == "Deployment" && document.Name == "fc-distribution");
|
||||
var distributionContainer = distributionDeployment.MainContainerMappings().Should().ContainSingle().Subject;
|
||||
|
||||
ManifestNodeExtensions.Scalar(distributionContainer, "image").Should().Be("localhost/fc-distribution:v20260604-oidc-root-anon");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MonitoringProbes_UseHealthzForOidcGatedHosts()
|
||||
{
|
||||
var monitoring = File.ReadAllText(Path.Combine(Inventory.BluejayRoot, "apps", "monitoring", "noc-monitoring.yaml"));
|
||||
|
||||
monitoring.Should().Contain("\"https://dns.iamworkin.lan/healthz\"");
|
||||
monitoring.Should().Contain("\"https://dist.iamworkin.lan/healthz\"");
|
||||
monitoring.Should().Contain("\"https://media.iamworkin.lan/healthz\"");
|
||||
monitoring.Should().NotContain("\"https://dns.iamworkin.lan/\"");
|
||||
monitoring.Should().NotContain("\"https://dist.iamworkin.lan/\"");
|
||||
monitoring.Should().NotContain("\"https://media.iamworkin.lan/\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DistributionPublicIngress_KeepsGetHeadMethodAllowlist()
|
||||
{
|
||||
var publicIngress = AppDocuments("fc-distribution")
|
||||
.Single(document => document.Kind == "IngressRoute" && document.Name == "fc-distribution-public");
|
||||
var route = publicIngress.MappingSequence("spec", "routes").Should().ContainSingle().Subject;
|
||||
var match = ManifestNodeExtensions.Scalar(route, "match");
|
||||
|
||||
match.Should().Contain("Host(`dist.flowercore.io`)");
|
||||
match.Should().Contain("Method(`GET`)");
|
||||
match.Should().Contain("Method(`HEAD`)");
|
||||
match.Should().NotContain("Method(`POST`)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DnsAndMediaIngressRoutes_MatchLiveInternalHosts()
|
||||
{
|
||||
var dnsRoute = AppDocuments("fc-dns")
|
||||
.Single(document => document.Kind == "IngressRoute" && document.Name == "dns-web")
|
||||
.MappingSequence("spec", "routes")
|
||||
.Should()
|
||||
.ContainSingle()
|
||||
.Subject;
|
||||
var mediaRoute = AppDocuments("fc-media")
|
||||
.Single(document => document.Kind == "IngressRoute" && document.Name == "fc-media-web")
|
||||
.MappingSequence("spec", "routes")
|
||||
.Should()
|
||||
.ContainSingle()
|
||||
.Subject;
|
||||
|
||||
ManifestNodeExtensions.Scalar(dnsRoute, "match").Should().Be("Host(`dns.iamworkin.lan`)");
|
||||
ManifestNodeExtensions.Scalar(mediaRoute, "match").Should().Be("Host(`media.iamworkin.lan`)");
|
||||
.Be("http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git");
|
||||
application.Scalar("spec", "source", "path").Should().Be("apps/fc-devicemgmt");
|
||||
application.Scalar("spec", "destination", "namespace").Should().Be("fc-devicemgmt");
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ProbeViolations(
|
||||
@@ -1058,44 +762,12 @@ public sealed class FleetManifestLintTests
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string? EnvSecretOptional(YamlMappingNode container, string name)
|
||||
{
|
||||
return EnvMapping(container, name) is { } env
|
||||
? ManifestNodeExtensions.Scalar(env, "valueFrom", "secretKeyRef", "optional")
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string? ProbePath(YamlMappingNode container, string probeKey)
|
||||
{
|
||||
return ManifestNodeExtensions.Scalar(container, probeKey, "httpGet", "path");
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ManifestDocument> AppDocuments(string app)
|
||||
{
|
||||
return Inventory.Documents
|
||||
.Where(document => document.RelativePath.StartsWith($"{app}/", StringComparison.Ordinal))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static YamlMappingNode? EnvMapping(YamlMappingNode container, string name)
|
||||
{
|
||||
return ManifestNodeExtensions.MappingSequence(container, "env")
|
||||
.SingleOrDefault(env => string.Equals(ManifestNodeExtensions.Scalar(env, "name"), name, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private static string? PodAnnotation(ManifestDocument document, string name)
|
||||
{
|
||||
return document.Scalar("spec", "template", "metadata", "annotations", name);
|
||||
}
|
||||
|
||||
private static string? ProbeHttpGetPath(YamlMappingNode container, string probeKey)
|
||||
{
|
||||
return ManifestNodeExtensions.TryGetMapping(container, probeKey, out var probe)
|
||||
&& ManifestNodeExtensions.TryGetMapping(probe, "httpGet", out var httpGet)
|
||||
? ManifestNodeExtensions.Scalar(httpGet, "path")
|
||||
: null;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ManifestDocument> FcDeviceManagementDocuments()
|
||||
{
|
||||
return Inventory.Documents
|
||||
|
||||
124
tests/bluejay-infra-lint/MonitoringCoverageLintTests.cs
Normal file
124
tests/bluejay-infra-lint/MonitoringCoverageLintTests.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using FluentAssertions;
|
||||
using System.Text.RegularExpressions;
|
||||
using Xunit;
|
||||
|
||||
namespace BluejayInfraLint.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class MonitoringCoverageLintTests
|
||||
{
|
||||
private static readonly ManifestInventory Inventory = ManifestInventory.Load();
|
||||
|
||||
private static readonly string[] Sprint57ProbeTargets =
|
||||
{
|
||||
"https://dns.iamworkin.lan/",
|
||||
"https://flowercore.iamworkin.lan/healthz",
|
||||
"https://replay.iamworkin.lan/healthz",
|
||||
"https://signalcontrol.iamworkin.lan/health",
|
||||
"https://updatecenter-internal.iamworkin.lan/api/v1/manifests/_schema",
|
||||
"https://updates.iamworkin.lan/api/v1/manifests/_schema",
|
||||
"https://worldbuilder.iamworkin.lan/healthz",
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void PrometheusScrape_MustNotTargetDeadPiManagerPort()
|
||||
{
|
||||
var monitoring = ReadMonitoringMirror();
|
||||
|
||||
monitoring.Should().NotContain("10.0.58.113:5100");
|
||||
monitoring.Should().Contain("10.0.58.113:5200");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProbeJobs_MustKeepEnvironmentSpecificBlackboxRelabels()
|
||||
{
|
||||
var monitoring = ReadMonitoringMirror();
|
||||
var probeJobs = FindProbeJobs(monitoring);
|
||||
|
||||
probeJobs.Should().NotBeEmpty();
|
||||
probeJobs.Should().OnlyContain(
|
||||
job => job.Contains("replacement: blackbox-exporter.monitoring.svc:9115", StringComparison.Ordinal),
|
||||
"the bluejay-infra mirror runs Prometheus in-cluster and should use the blackbox service DNS");
|
||||
|
||||
var livePodmanPrometheus = TryReadNotesMonitoringFile("prometheus.yml");
|
||||
if (livePodmanPrometheus is not null)
|
||||
{
|
||||
FindProbeJobs(livePodmanPrometheus).Should().OnlyContain(
|
||||
job => job.Contains("replacement: localhost:9115", StringComparison.Ordinal),
|
||||
"live Podman monitoring uses host networking, so blackbox probes must relabel to localhost:9115");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TraefikServiceProbes_MustCoverSprint57LiveFlowerCoreHosts()
|
||||
{
|
||||
var monitoring = ReadMonitoringMirror();
|
||||
|
||||
foreach (var target in Sprint57ProbeTargets)
|
||||
{
|
||||
monitoring.Should().Contain(target);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EpsonPrinterDown_MustUseRangeWindowForStaleScrapeCoverage()
|
||||
{
|
||||
var alerts = ReadMonitoringMirror();
|
||||
|
||||
alerts.Should().Contain("- alert: EpsonPrinterDown");
|
||||
alerts.Should().Contain("max_over_time(up{job=\"snmp-printer\"}[35m]) == bool 0");
|
||||
alerts.Should().NotContain("expr: up{job=\"snmp-printer\"} == 0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MonitoringMirror_MustCarryRunnerExclusionsAndEpsonGrafanaDelivery()
|
||||
{
|
||||
var mirror = ReadMonitoringMirror();
|
||||
|
||||
GetAlertBlock(mirror, "KubeContainerRestartingFrequently")
|
||||
.Should()
|
||||
.Contain("kube_pod_container_status_restarts_total{namespace!=\"github-runner\"}[1h]");
|
||||
GetAlertBlock(mirror, "KubeContainerCrashLooping")
|
||||
.Should()
|
||||
.Contain("kube_pod_container_status_restarts_total{namespace!=\"github-runner\"}[15m]");
|
||||
GetAlertBlock(mirror, "KubeDeploymentReplicasMismatch")
|
||||
.Should()
|
||||
.Contain("kube_deployment_spec_replicas{namespace!=\"github-runner\"} != kube_deployment_status_replicas_available{namespace!=\"github-runner\"}");
|
||||
mirror.Should().Contain("uid: epson-printer-down-stale-window");
|
||||
mirror.Should().Contain("title: EpsonPrinterDown");
|
||||
mirror.Should().Contain("alert_channel: irc");
|
||||
}
|
||||
|
||||
private static string ReadMonitoringMirror() =>
|
||||
File.ReadAllText(Path.Combine(Inventory.BluejayRoot, "apps", "monitoring", "noc-monitoring.yaml"));
|
||||
|
||||
private static string? TryReadNotesMonitoringFile(string fileName)
|
||||
{
|
||||
var overrideRoot = Environment.GetEnvironmentVariable("FLOWERCORE_NOTES_ROOT");
|
||||
if (string.IsNullOrWhiteSpace(overrideRoot))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var path = Path.Combine(overrideRoot, "scripts", "monitoring", fileName);
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> FindProbeJobs(string yaml) =>
|
||||
Regex.Matches(
|
||||
yaml,
|
||||
"(?ms)^\\s+- job_name: \"probe-[^\"]+\".*?(?=^\\s+- job_name:|\\z)")
|
||||
.Cast<Match>()
|
||||
.Select(match => match.Value)
|
||||
.ToList();
|
||||
|
||||
private static string GetAlertBlock(string yaml, string alertName)
|
||||
{
|
||||
var match = Regex.Match(
|
||||
yaml,
|
||||
$"(?ms)^\\s+- alert: {Regex.Escape(alertName)}\\s*$.*?(?=^\\s+- alert:|\\z)");
|
||||
|
||||
match.Success.Should().BeTrue($"alert {alertName} should be present in noc-monitoring.yaml");
|
||||
return match.Value;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user