Compare commits
30 Commits
runners/ad
...
codex/s57-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed9b989fa | ||
|
|
404d884863 | ||
| f4bd90f805 | |||
|
|
67d67ab73d | ||
|
|
f7d41cdc60 | ||
|
|
2c0afc28e4 | ||
|
|
ba5f5dd0fb | ||
|
|
dc699da7b3 | ||
|
|
1e8bf54c6e | ||
|
|
e2e93d482c | ||
| 4319cc2b51 | |||
|
|
2bf339ce51 | ||
|
|
5bdedfc5ae | ||
|
|
0307ae16ae | ||
|
|
6c18f69cf2 | ||
|
|
47e2256556 | ||
|
|
9d77f8ba0e | ||
|
|
2f4be19c85 | ||
|
|
2a62c40990 | ||
|
|
7be98e5efc | ||
|
|
a65b356c9d | ||
|
|
08c17ef1b4 | ||
|
|
06f2f002b7 | ||
|
|
7ac4a8b4b7 | ||
|
|
90f2a86819 | ||
|
|
cbdefb2b23 | ||
|
|
1c36fe3a0a | ||
|
|
2b420ce8a4 | ||
|
|
5cbc1a06b1 | ||
|
|
9e7ee39b3a |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/.gitattributes text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
10
README.md
10
README.md
@@ -116,6 +116,16 @@ dotnet test tests/bluejay-infra-lint/BluejayInfraLint.Tests.csproj -c Release
|
|||||||
|
|
||||||
That test project sweeps `bluejay-infra/apps/**` plus the canonical sibling `FlowerCore.*\\k8s` manifests that share the same workspace. Matching `conftest.dev` policy files live under `tests/bluejay-infra-lint/conftest.dev/` for environments that also have `conftest` or `opa`.
|
That test project sweeps `bluejay-infra/apps/**` plus the canonical sibling `FlowerCore.*\\k8s` manifests that share the same workspace. Matching `conftest.dev` policy files live under `tests/bluejay-infra-lint/conftest.dev/` for environments that also have `conftest` or `opa`.
|
||||||
|
|
||||||
|
## Non-K8s Pi Artifacts
|
||||||
|
|
||||||
|
Some `apps/*` directories are deployment artifact bundles consumed by Puppet
|
||||||
|
instead of Kubernetes workloads. `apps/fc-signage-pi-player/` carries the
|
||||||
|
Chromium signage Pi player, `apps/fc-divoom-dm-pi-device/` carries the additive
|
||||||
|
edge2 Divoom-as-DeviceManagement-device profile/Hiera contract, and
|
||||||
|
`apps/fc-divoom-tv-pi/` carries the Divoom TV Pi HDMI systemd/Puppet shape.
|
||||||
|
These bundles intentionally avoid Deployment, IngressRoute, Certificate, and
|
||||||
|
OnePasswordItem resources.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- OpenVox noc1 durability runbook: `docs/runbooks/openvoxserver-quadlet-durability.md`
|
- OpenVox noc1 durability runbook: `docs/runbooks/openvoxserver-quadlet-durability.md`
|
||||||
|
|||||||
169
apps/fc-aistation/fc-aistation.yaml
Normal file
169
apps/fc-aistation/fc-aistation.yaml
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# FlowerCore.AiStation.Web GitOps adoption manifest.
|
||||||
|
#
|
||||||
|
# Authored from the already-live fc-aistation resources on 2026-06-04.
|
||||||
|
# Keep the live image tag, Service ClusterIP, and PVC volumeName unchanged so
|
||||||
|
# ArgoCD adopts in place instead of replacing the workload or data volume.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: aistation-web-data
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
volumeName: pvc-27448d6f-6e66-42a7-a293-73dd8bbd6b3e
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: aistation-web
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
progressDeadlineSeconds: 600
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
prometheus.io/path: /metrics/prometheus
|
||||||
|
prometheus.io/port: "5000"
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: aistation-web-config
|
||||||
|
image: localhost/fc-aistation-web:v20260602-aistation-owned-deploy-fix2
|
||||||
|
imagePullPolicy: Never
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 5
|
||||||
|
name: aistation-web
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 6
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 5
|
||||||
|
resources: {}
|
||||||
|
terminationMessagePath: /dev/termination-log
|
||||||
|
terminationMessagePolicy: File
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /data
|
||||||
|
name: data
|
||||||
|
dnsPolicy: ClusterFirst
|
||||||
|
restartPolicy: Always
|
||||||
|
schedulerName: default-scheduler
|
||||||
|
securityContext: {}
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: aistation-web-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: aistation-web
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.43.211.127
|
||||||
|
clusterIPs:
|
||||||
|
- 10.43.211.127
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: aistation-web-tls
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web-tls
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
dnsNames:
|
||||||
|
- aistation.iamworkin.lan
|
||||||
|
issuerRef:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: step-ca-acme
|
||||||
|
secretName: aistation-web-tls
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: aistation-web
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`aistation.iamworkin.lan`)
|
||||||
|
services:
|
||||||
|
- name: aistation-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: aistation-web-tls
|
||||||
@@ -1,5 +1,206 @@
|
|||||||
# FlowerCore Chat — TLS + Ingress
|
# FlowerCore Chat
|
||||||
# Deployment and Service managed by deploy script (not ArgoCD)
|
#
|
||||||
|
# ArgoCD-managed workload plus TLS/Ingress. The chat-web-secret remains an
|
||||||
|
# out-of-band Secret until the values are moved into a 1Password-backed item;
|
||||||
|
# the Deployment references it as optional so GitOps can own the workload
|
||||||
|
# without storing secret material in this repo.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: chat-web-config
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
data:
|
||||||
|
ASPNETCORE_ENVIRONMENT: Production
|
||||||
|
ASPNETCORE_URLS: "http://+:8080"
|
||||||
|
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
|
||||||
|
FlowerCore__Auth__Enabled: "false"
|
||||||
|
FlowerCore__Auth__Oidc__Enabled: "true"
|
||||||
|
FlowerCore__Auth__Oidc__Authority: "https://id.iamworkin.lan/application/o/chat/"
|
||||||
|
FlowerCore__Auth__Oidc__Audience: "chat"
|
||||||
|
FlowerCore__Auth__Oidc__ClientId: "chat"
|
||||||
|
FlowerCore__Database__ConnectionStrings__Sqlite: "Data Source=/data/chat.db"
|
||||||
|
# Ollama target. Switched 2026-04-25 from edge1 Pi5 (10.0.57.17) to BLUEJAY-WS
|
||||||
|
# workstation (10.0.56.20, RX 9070 XT 16GB, OLLAMA_HOST=0.0.0.0:11434, Vulkan
|
||||||
|
# backend per feedback_rdna4_vulkan_broken). The Pi5 was timing out every team-
|
||||||
|
# round speaker at the 300s per-turn cap (live-proven 2026-04-25 03:53 UTC,
|
||||||
|
# see feedback_chat_team_round_edge1_too_slow). Workstation has gemma3:4b for
|
||||||
|
# the Cheap tier, plus gemma3:27b/phi4:14b/qwen3:14b for Default/Balanced/Deep.
|
||||||
|
# Piper TTS stays on edge1 below (different service, Pi handles TTS fine).
|
||||||
|
FlowerCore__AI__OllamaBaseUrl: "http://10.0.56.20:11434"
|
||||||
|
FlowerCore__AI__DefaultModelName: "phi4:14b"
|
||||||
|
ChatOptions__BehaviorRuleEngine__OllamaBaseUrl: "http://10.0.56.20:11434"
|
||||||
|
ChatOptions__BehaviorRuleEngine__FallbackOllamaBaseUrl: "http://10.0.57.17:11434"
|
||||||
|
ChatOptions__BehaviorRuleEngine__ModelName: "gemma3:12b"
|
||||||
|
FlowerCore__AI__Memory__UseSharedIndexingAdapter: "true"
|
||||||
|
FlowerCore__AI__Memory__UseOllamaEmbeddings: "true"
|
||||||
|
FlowerCore__AI__Memory__EmbeddingModel: "nomic-embed-text"
|
||||||
|
FlowerCore__AI__Memory__EnableSharedIndexingBackfill: "true"
|
||||||
|
FlowerCore__AI__Memory__SharedIndexingDatabasePath: "/data/chat-memory-index.db"
|
||||||
|
FlowerCore__AI__Skills__Library__LibraryApiUrl: "http://library-web.fc-library.svc.cluster.local"
|
||||||
|
FlowerCore__AI__Skills__Retail__RetailApiUrl: "http://retail-web.fc-retail.svc.cluster.local"
|
||||||
|
FlowerCore__AI__Skills__Intranet__IntranetBaseUrl: "http://intranet-web.intranet.svc.cluster.local"
|
||||||
|
FlowerCore__AI__Skills__Print__PrintMcpBaseUrl: "http://10.0.57.16:5200"
|
||||||
|
FlowerCore__AI__IrcBridge__Enabled: "true"
|
||||||
|
FlowerCore__AI__IrcBridge__DefaultProfileSlug: "it-helpdesk"
|
||||||
|
FlowerCore__AI__IrcBridge__MentionProfileSlug: "it-helpdesk"
|
||||||
|
FlowerCore__AI__IrcBridge__MentionReactiveMode: "mentions-only"
|
||||||
|
FlowerCore__AI__IrcBridge__AllowActionExecution: "false"
|
||||||
|
FlowerCore__AI__Voice__Piper__Host: "10.0.57.17"
|
||||||
|
FlowerCore__AI__Voice__Piper__Port: "10400"
|
||||||
|
FlowerCore__AI__Voice__OutputRoot: "/data/audio"
|
||||||
|
FlowerCore__AI__Voice__RetentionDays: "30"
|
||||||
|
# LLM provider abstraction (ADR-088). Anthropic stays disabled here -- when
|
||||||
|
# an operator wants to enable Claude, they flip Enabled=true and mount
|
||||||
|
# FlowerCore__Anthropic__ApiKey from the onepassword-synced Secret (see
|
||||||
|
# docs/ai-agents/anthropic-integration.md).
|
||||||
|
FlowerCore__Anthropic__Enabled: "false"
|
||||||
|
FlowerCore__Anthropic__BaseUrl: "https://api.anthropic.com"
|
||||||
|
FlowerCore__Anthropic__DefaultModel: "claude-sonnet-4-6"
|
||||||
|
FlowerCore__Anthropic__CheapModel: "claude-haiku-4-5-20251001"
|
||||||
|
FlowerCore__Anthropic__DeepModel: "claude-opus-4-7"
|
||||||
|
FlowerCore__Budget__ResponseCacheEnabled: "true"
|
||||||
|
OTEL_SERVICE_NAME: FlowerCore.Chat
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.monitoring.svc.cluster.local:4317"
|
||||||
|
OTEL_EXPORTER_OTLP_PROTOCOL: grpc
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: chat-web-data
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: chat-web
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8080"
|
||||||
|
prometheus.io/path: "/metrics/prometheus"
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: rke2-server
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1654
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
containers:
|
||||||
|
- name: chat-web
|
||||||
|
image: localhost/fc-chat-web:v20260603-oidc-authentik
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: chat-web-config
|
||||||
|
- secretRef:
|
||||||
|
name: chat-web-secret
|
||||||
|
optional: true
|
||||||
|
env:
|
||||||
|
- name: FlowerCore__Auth__Oidc__Authority
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chat-oidc-client
|
||||||
|
key: issuer_url
|
||||||
|
optional: true
|
||||||
|
- name: FlowerCore__Auth__Oidc__ClientId
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chat-oidc-client
|
||||||
|
key: client_id
|
||||||
|
optional: true
|
||||||
|
- name: FlowerCore__Auth__Oidc__ClientSecret
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chat-oidc-client
|
||||||
|
key: client_secret
|
||||||
|
optional: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 6
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: chat-web-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: chat-web
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
---
|
---
|
||||||
apiVersion: cert-manager.io/v1
|
apiVersion: cert-manager.io/v1
|
||||||
kind: Certificate
|
kind: Certificate
|
||||||
|
|||||||
45
apps/fc-divoom-dm-pi-device/README.md
Normal file
45
apps/fc-divoom-dm-pi-device/README.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# FlowerCore Divoom DM Pi Device
|
||||||
|
|
||||||
|
Source-controlled Puppet/Hiera deployment contract for registering the edge2
|
||||||
|
Divoom MiniToo panel as a FlowerCore DeviceManagement-managed Pi device.
|
||||||
|
|
||||||
|
This is not a Kubernetes application. The live panel remains the existing
|
||||||
|
edge2 `flowercore-divoom.service` managed by `FlowerCore.Puppet`
|
||||||
|
`profile::pi::service::divoom`, with the .NET payload deployed out of band
|
||||||
|
and `/opt/flowercore/divoom/data` plus the Bluetooth shell wrappers preserved.
|
||||||
|
Because edge2 is already Hiera-driven through `profile::pi::service::apps`,
|
||||||
|
the deploy home is additive `profile::pi::service` data/profile source, not
|
||||||
|
`profile::edge::service::apps` and not an ArgoCD/K8s app.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Stage DeviceManagement registration metadata for the edge2 Divoom MiniToo.
|
||||||
|
- Stage a separate, disabled-by-default DM Agent executor unit for privileged
|
||||||
|
Bluetooth operations once the DM-RPC lane lands.
|
||||||
|
- Keep `flowercore-divoom.service` and `flowercore-divoom-bt.service`
|
||||||
|
untouched: no service replacement, no restart subscription, no K8s surface.
|
||||||
|
- Preserve the current wrapper contract:
|
||||||
|
`/opt/flowercore/divoom/bt-link.sh`,
|
||||||
|
`/opt/flowercore/divoom/bt-reset.sh`, and
|
||||||
|
`/opt/flowercore/divoom/audio-link.sh`.
|
||||||
|
- Keep FM radio disabled and require visible render proof; device-info echo is
|
||||||
|
not render proof.
|
||||||
|
|
||||||
|
## Artifact Map
|
||||||
|
|
||||||
|
| Path | Use |
|
||||||
|
| --- | --- |
|
||||||
|
| `hiera/edge2-divoom-dm-device.overlay.yaml` | Additive Hiera overlay for edge2. Merge into the existing node YAML without removing `fc-pimanager` or `fc-divoom`. |
|
||||||
|
| `puppet/profile/pi/service/divoom_dm_device.pp` | Puppet profile shape to vendor into `FlowerCore.Puppet` after the DM-RPC executor binary exists. |
|
||||||
|
| `puppet/templates/divoom-device-registration.json.epp` | DM device registration metadata rendered on edge2. |
|
||||||
|
| `puppet/templates/flowercore-divoom-dm-agent.service.epp` | Separate DM Agent systemd unit. Defaults are stopped and disabled until a later cutover. |
|
||||||
|
|
||||||
|
## Rollout Notes
|
||||||
|
|
||||||
|
1. Land these artifacts in bluejay-infra as the deploy contract.
|
||||||
|
2. Vendor the Puppet profile and EPP templates into `FlowerCore.Puppet`.
|
||||||
|
3. Merge the Hiera overlay into `data/nodes/edge2.iamworkin.lan.yaml`.
|
||||||
|
4. Run Puppet in noop first, preferably with a node-local validation directory
|
||||||
|
under `~/.fcv` rather than `/tmp`.
|
||||||
|
5. Only enable the DM Agent service after the DeviceManagement BT executor has
|
||||||
|
landed and passed operator-eyeball render proof.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
# Merge into FlowerCore.Puppet data/nodes/edge2.iamworkin.lan.yaml.
|
||||||
|
# Additive overlay only: keep the existing fc-pimanager version/tarball entry,
|
||||||
|
# keep fc-divoom enabled, and do not move Divoom into Kubernetes.
|
||||||
|
|
||||||
|
profile::pi::service::apps:
|
||||||
|
fc-pimanager:
|
||||||
|
binary: 'FlowerCore.PiManager.Web'
|
||||||
|
install_dir: '/opt/fc-pimanager'
|
||||||
|
port: 5000
|
||||||
|
environment: 'edge2'
|
||||||
|
version: '2026.05.28.1646'
|
||||||
|
tarball_source: 'puppet:///modules/profile/pi/builds/fc-pimanager.tar.gz'
|
||||||
|
fc-divoom:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
profile::pi::service::divoom_dm_device::ensure: 'present'
|
||||||
|
profile::pi::service::divoom_dm_device::service_enabled: false
|
||||||
|
profile::pi::service::divoom_dm_device::service_ensure: 'stopped'
|
||||||
|
profile::pi::service::divoom_dm_device::device_id: 'edge2-divoom-minitoo'
|
||||||
|
profile::pi::service::divoom_dm_device::display_name: 'edge2 Divoom MiniToo'
|
||||||
|
profile::pi::service::divoom_dm_device::host_fqdn: 'edge2.iamworkin.lan'
|
||||||
|
profile::pi::service::divoom_dm_device::dm_web_url: 'https://devicemgmt.iamworkin.lan'
|
||||||
|
profile::pi::service::divoom_dm_device::divoom_install_dir: '/opt/flowercore/divoom'
|
||||||
|
profile::pi::service::divoom_dm_device::agent_install_dir: '/opt/flowercore/devicemanagement-agent'
|
||||||
|
profile::pi::service::divoom_dm_device::bt_candidate_channels:
|
||||||
|
- '1'
|
||||||
|
- '10'
|
||||||
|
profile::pi::service::divoom_dm_device::default_bt_channel: '1'
|
||||||
|
profile::pi::service::divoom_dm_device::a2dp_default_state: 'off'
|
||||||
|
profile::pi::service::divoom_dm_device::fm_radio_enabled: false
|
||||||
|
profile::pi::service::divoom_dm_device::visible_render_proof_required: true
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Drop into FlowerCore.Puppet site-modules/profile/manifests/pi/service/divoom_dm_device.pp.
|
||||||
|
# This profile is additive to profile::pi::service::divoom. It must not manage,
|
||||||
|
# restart, replace, or subscribe the existing flowercore-divoom.service.
|
||||||
|
class profile::pi::service::divoom_dm_device (
|
||||||
|
Enum['present', 'absent'] $ensure = 'present',
|
||||||
|
Boolean $service_enabled = false,
|
||||||
|
Enum['running', 'stopped'] $service_ensure = 'stopped',
|
||||||
|
String $service_name = 'flowercore-divoom-dm-agent',
|
||||||
|
String $device_id = 'edge2-divoom-minitoo',
|
||||||
|
String $display_name = 'edge2 Divoom MiniToo',
|
||||||
|
String $host_fqdn = 'edge2.iamworkin.lan',
|
||||||
|
String $dm_web_url = 'https://devicemgmt.iamworkin.lan',
|
||||||
|
String $divoom_install_dir = '/opt/flowercore/divoom',
|
||||||
|
String $agent_install_dir = '/opt/flowercore/devicemanagement-agent',
|
||||||
|
String $agent_binary = 'FlowerCore.DeviceManagement.Agent',
|
||||||
|
Array[String] $bt_candidate_channels = ['1', '10'],
|
||||||
|
String $default_bt_channel = '1',
|
||||||
|
Enum['on', 'off'] $a2dp_default_state = 'off',
|
||||||
|
Boolean $fm_radio_enabled = false,
|
||||||
|
Boolean $visible_render_proof_required = true,
|
||||||
|
) {
|
||||||
|
include profile::workstation::safe_account_exclusion
|
||||||
|
|
||||||
|
$safe_account = $profile::workstation::safe_account_exclusion::safe_account
|
||||||
|
$config_dir = '/etc/flowercore/device-management/devices'
|
||||||
|
$state_dir = '/var/lib/flowercore/divoom-dm-agent'
|
||||||
|
$log_dir = '/var/log/flowercore/divoom-dm-agent'
|
||||||
|
$registration_path = "${config_dir}/${device_id}.json"
|
||||||
|
$agent_binary_path = "${agent_install_dir}/${agent_binary}"
|
||||||
|
$bt_channels_json = inline_template('[<%= @bt_candidate_channels.map { |c| "\"#{c}\"" }.join(", ") %>]')
|
||||||
|
|
||||||
|
if $safe_account {
|
||||||
|
notify { 'fc-divoom-dm-device safe-account exclusion':
|
||||||
|
message => 'SAFE-ACCOUNT-EXCLUSION: Divoom DM Pi device profile refused to apply on operator workstation',
|
||||||
|
}
|
||||||
|
|
||||||
|
if $facts['os']['family'] != 'windows' {
|
||||||
|
ensure_resource('file', '/var/log/flowercore-audit', {
|
||||||
|
'ensure' => 'directory',
|
||||||
|
'owner' => 'root',
|
||||||
|
'group' => 'root',
|
||||||
|
'mode' => '0755',
|
||||||
|
})
|
||||||
|
|
||||||
|
file { '/var/log/flowercore-audit/safe-account-noop-fc-divoom-dm-device.log':
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => "noop: divoom dm pi device profile refused to apply on safe-account host\n",
|
||||||
|
require => File['/var/log/flowercore-audit'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elsif $ensure == 'absent' {
|
||||||
|
service { $service_name:
|
||||||
|
ensure => stopped,
|
||||||
|
enable => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [
|
||||||
|
"/etc/systemd/system/${service_name}.service",
|
||||||
|
$registration_path,
|
||||||
|
]:
|
||||||
|
ensure => absent,
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-dm-agent-systemd-reload':
|
||||||
|
command => '/usr/bin/systemctl daemon-reload',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
case $facts['os']['family'] {
|
||||||
|
'Debian': {}
|
||||||
|
default: { fail("profile::pi::service::divoom_dm_device only supports Debian-family OS, got ${facts['os']['family']}") }
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [$config_dir, $state_dir, $log_dir]:
|
||||||
|
ensure => directory,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0755',
|
||||||
|
}
|
||||||
|
|
||||||
|
file { $registration_path:
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => epp('profile/pi/fc_divoom_dm/divoom-device-registration.json.epp', {
|
||||||
|
'device_id' => $device_id,
|
||||||
|
'display_name' => $display_name,
|
||||||
|
'host_fqdn' => $host_fqdn,
|
||||||
|
'divoom_install_dir' => $divoom_install_dir,
|
||||||
|
'bt_channels_json' => $bt_channels_json,
|
||||||
|
'default_bt_channel' => $default_bt_channel,
|
||||||
|
'a2dp_default_state' => $a2dp_default_state,
|
||||||
|
'fm_radio_enabled' => $fm_radio_enabled,
|
||||||
|
'visible_render_proof_required' => $visible_render_proof_required,
|
||||||
|
}),
|
||||||
|
require => File[$config_dir],
|
||||||
|
}
|
||||||
|
|
||||||
|
file { "/etc/systemd/system/${service_name}.service":
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => epp('profile/pi/fc_divoom_dm/flowercore-divoom-dm-agent.service.epp', {
|
||||||
|
'service_name' => $service_name,
|
||||||
|
'device_id' => $device_id,
|
||||||
|
'dm_web_url' => $dm_web_url,
|
||||||
|
'registration_path' => $registration_path,
|
||||||
|
'divoom_install_dir' => $divoom_install_dir,
|
||||||
|
'agent_install_dir' => $agent_install_dir,
|
||||||
|
'agent_binary_path' => $agent_binary_path,
|
||||||
|
'state_dir' => $state_dir,
|
||||||
|
'log_dir' => $log_dir,
|
||||||
|
}),
|
||||||
|
notify => Exec['fc-divoom-dm-agent-systemd-reload'],
|
||||||
|
require => File[$registration_path],
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-dm-agent-systemd-reload':
|
||||||
|
command => '/usr/bin/systemctl daemon-reload',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
|
||||||
|
service { $service_name:
|
||||||
|
ensure => $service_ensure,
|
||||||
|
enable => $service_enabled,
|
||||||
|
require => [
|
||||||
|
File["/etc/systemd/system/${service_name}.service"],
|
||||||
|
File[$registration_path],
|
||||||
|
Exec['fc-divoom-dm-agent-systemd-reload'],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"deviceId": "<%= $device_id %>",
|
||||||
|
"displayName": "<%= $display_name %>",
|
||||||
|
"hostFqdn": "<%= $host_fqdn %>",
|
||||||
|
"kind": "DivoomMiniToo",
|
||||||
|
"managedBy": "FlowerCore.DeviceManagement",
|
||||||
|
"executionMode": "Pi",
|
||||||
|
"transport": {
|
||||||
|
"kind": "BluetoothSerial",
|
||||||
|
"candidateChannels": <%= $bt_channels_json %>,
|
||||||
|
"defaultChannel": "<%= $default_bt_channel %>",
|
||||||
|
"deviceInfoIsRenderProof": false,
|
||||||
|
"visibleRenderProofRequired": <%= $visible_render_proof_required %>
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"divoomInstallDir": "<%= $divoom_install_dir %>",
|
||||||
|
"btLink": "<%= $divoom_install_dir %>/bt-link.sh",
|
||||||
|
"btReset": "<%= $divoom_install_dir %>/bt-reset.sh",
|
||||||
|
"audioLink": "<%= $divoom_install_dir %>/audio-link.sh"
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"supportsBluetoothSerial": true,
|
||||||
|
"supportsBtChannelRedetect": true,
|
||||||
|
"supportsBtHardReset": true,
|
||||||
|
"supportsBtAudioProfileSwitch": true,
|
||||||
|
"a2dpDefaultState": "<%= $a2dp_default_state %>",
|
||||||
|
"fmRadioEnabled": <%= $fm_radio_enabled %>
|
||||||
|
},
|
||||||
|
"safety": {
|
||||||
|
"preserveExistingService": "flowercore-divoom.service",
|
||||||
|
"preserveDataDirectory": "<%= $divoom_install_dir %>/data",
|
||||||
|
"doNotEnableFmRadio": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Divoom DM Agent Bluetooth executor
|
||||||
|
Documentation=https://github.com/astoltz/FlowerCore.Notes/blob/master/docs/standards/divoom-tv-hdmi-multitarget-render-substrate.md
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target bluetooth.service
|
||||||
|
Requires=bluetooth.service
|
||||||
|
ConditionPathExists=<%= $agent_binary_path %>
|
||||||
|
ConditionPathExists=<%= $registration_path %>
|
||||||
|
ConditionPathExists=<%= $divoom_install_dir %>/bt-link.sh
|
||||||
|
ConditionPathExists=<%= $divoom_install_dir %>/bt-reset.sh
|
||||||
|
ConditionPathExists=<%= $divoom_install_dir %>/audio-link.sh
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=stoltz
|
||||||
|
Group=stoltz
|
||||||
|
WorkingDirectory=<%= $agent_install_dir %>
|
||||||
|
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
Environment=FLOWERCORE_DM_DEVICE_REGISTRATION=<%= $registration_path %>
|
||||||
|
Environment=Divoom__Bluetooth__DeviceInfoIsRenderProof=false
|
||||||
|
Environment=Divoom__Bluetooth__VisibleRenderProofRequired=true
|
||||||
|
Environment=Divoom__Bluetooth__A2dpDefaultState=off
|
||||||
|
ExecStart=<%= $agent_binary_path %> --mode=Pi --device-id=<%= $device_id %> --dm-web-url=<%= $dm_web_url %> --registration=<%= $registration_path %>
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10s
|
||||||
|
StartLimitBurst=3
|
||||||
|
StartLimitIntervalSec=300s
|
||||||
|
SupplementaryGroups=bluetooth audio dialout
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=<%= $state_dir %> <%= $log_dir %>
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
44
apps/fc-divoom-tv-pi/README.md
Normal file
44
apps/fc-divoom-tv-pi/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# FlowerCore Divoom TV Pi HDMI
|
||||||
|
|
||||||
|
Source-controlled deploy shape for the native `FlowerCore.Divoom.Tv`
|
||||||
|
Avalonia HDMI renderer on a Raspberry Pi connected to a TV.
|
||||||
|
|
||||||
|
This is a Puppet/systemd appliance bundle, not a Kubernetes application. It
|
||||||
|
mirrors the existing `fc-signage-pi-player` pattern: bluejay-infra carries the
|
||||||
|
systemd units, scripts, Hiera shape, and Puppet profile source that
|
||||||
|
`FlowerCore.Puppet` vendors and installs.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Launch the future `FlowerCore.Divoom.Tv` linux-arm64 self-contained payload
|
||||||
|
from `/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv`.
|
||||||
|
- Prefer `cage` as the Wayland fullscreen compositor, with direct app launch as
|
||||||
|
a fallback for development images.
|
||||||
|
- Restart the app after HDMI hotplug with a 2 second DRM settle delay.
|
||||||
|
- Keep all runtime state local: `/var/lib/fc-divoom-tv` and
|
||||||
|
`/var/log/fc-divoom-tv`.
|
||||||
|
- Avoid CDN/runtime fetches; the app renders the in-house Divoom scene catalog
|
||||||
|
locally.
|
||||||
|
|
||||||
|
## Artifact Map
|
||||||
|
|
||||||
|
| Path | Use |
|
||||||
|
| --- | --- |
|
||||||
|
| `systemd/flowercore-divoom-tv.service` | Fullscreen Avalonia HDMI app service. |
|
||||||
|
| `systemd/flowercore-divoom-tv-hdmi.service` | HDMI hotplug responder service. |
|
||||||
|
| `systemd/99-flowercore-divoom-tv-hdmi.rules` | DRM udev hotplug rule. |
|
||||||
|
| `scripts/flowercore-divoom-tv-prelaunch.sh` | Preflight checks and local directory creation. |
|
||||||
|
| `scripts/flowercore-divoom-tv-launch.sh` | Cage-first fullscreen launcher. |
|
||||||
|
| `scripts/flowercore-divoom-tv-hdmi-respond.sh` | Hotplug settle and restart script. |
|
||||||
|
| `puppet/profile/pi/service/divoom_tv.pp` | Puppet profile shape to vendor into `FlowerCore.Puppet`. |
|
||||||
|
| `hiera/example-divoom-tv-pi.iamworkin.lan.yaml` | Example node Hiera for a Divoom TV Pi. |
|
||||||
|
|
||||||
|
## Rollout Notes
|
||||||
|
|
||||||
|
1. Build `FlowerCore.Divoom.Tv` with `dotnet.exe publish -c Release -r linux-arm64 --self-contained`.
|
||||||
|
2. Stage the payload to `/opt/flowercore/divoom-tv/` through the standard noc1
|
||||||
|
jump path and avoid `/tmp` for unprivileged Pi scratch.
|
||||||
|
3. Vendor the profile and static files into `FlowerCore.Puppet`.
|
||||||
|
4. Run Puppet noop, then apply on the target Pi.
|
||||||
|
5. Prove deployment with `systemctl is-active flowercore-divoom-tv.service`,
|
||||||
|
journal lines showing frames presented, and a visible HDMI display check.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
# Example node data for a dedicated Pi -> HDMI -> TV Divoom renderer.
|
||||||
|
# Copy into FlowerCore.Puppet data/nodes/<hostname>.iamworkin.lan.yaml only
|
||||||
|
# after the Pi has a static DHCP/DNS entry and the linux-arm64 payload exists.
|
||||||
|
|
||||||
|
facts:
|
||||||
|
role: pi_prototype
|
||||||
|
|
||||||
|
profile::motd::role: 'Divoom TV HDMI Renderer'
|
||||||
|
|
||||||
|
profile::pi::service::divoom_tv::ensure: 'present'
|
||||||
|
profile::pi::service::divoom_tv::service_enabled: true
|
||||||
|
profile::pi::service::divoom_tv::service_ensure: 'running'
|
||||||
|
profile::pi::service::divoom_tv::install_dir: '/opt/flowercore/divoom-tv'
|
||||||
|
profile::pi::service::divoom_tv::state_dir: '/var/lib/fc-divoom-tv'
|
||||||
|
profile::pi::service::divoom_tv::log_dir: '/var/log/fc-divoom-tv'
|
||||||
|
profile::pi::service::divoom_tv::presentation_mode: 'PillarboxSquare'
|
||||||
|
profile::pi::service::divoom_tv::startup_scene: 'bluejay-clock'
|
||||||
|
profile::pi::service::divoom_tv::reduced_motion: false
|
||||||
149
apps/fc-divoom-tv-pi/puppet/profile/pi/service/divoom_tv.pp
Normal file
149
apps/fc-divoom-tv-pi/puppet/profile/pi/service/divoom_tv.pp
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Drop into FlowerCore.Puppet site-modules/profile/manifests/pi/service/divoom_tv.pp.
|
||||||
|
# Static files come from profile/pi/fc_divoom_tv/ after this bluejay-infra
|
||||||
|
# bundle is vendored into the Puppet control repo.
|
||||||
|
class profile::pi::service::divoom_tv (
|
||||||
|
Enum['present', 'absent'] $ensure = 'present',
|
||||||
|
Boolean $service_enabled = false,
|
||||||
|
Enum['running', 'stopped'] $service_ensure = 'stopped',
|
||||||
|
String $service_name = 'flowercore-divoom-tv',
|
||||||
|
String $user = 'fc-divoom-tv',
|
||||||
|
String $group = 'fc-divoom-tv',
|
||||||
|
String $install_dir = '/opt/flowercore/divoom-tv',
|
||||||
|
String $state_dir = '/var/lib/fc-divoom-tv',
|
||||||
|
String $log_dir = '/var/log/fc-divoom-tv',
|
||||||
|
String $presentation_mode = 'PillarboxSquare',
|
||||||
|
String $startup_scene = 'bluejay-clock',
|
||||||
|
Boolean $reduced_motion = false,
|
||||||
|
) {
|
||||||
|
include profile::workstation::safe_account_exclusion
|
||||||
|
|
||||||
|
$safe_account = $profile::workstation::safe_account_exclusion::safe_account
|
||||||
|
|
||||||
|
if $safe_account {
|
||||||
|
notify { 'fc-divoom-tv safe-account exclusion':
|
||||||
|
message => 'SAFE-ACCOUNT-EXCLUSION: Divoom TV Pi profile refused to apply on operator workstation',
|
||||||
|
}
|
||||||
|
} elsif $ensure == 'absent' {
|
||||||
|
service { $service_name:
|
||||||
|
ensure => stopped,
|
||||||
|
enable => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [
|
||||||
|
"/etc/systemd/system/${service_name}.service",
|
||||||
|
"/etc/systemd/system/${service_name}-hdmi.service",
|
||||||
|
'/etc/udev/rules.d/99-flowercore-divoom-tv-hdmi.rules',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-prelaunch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-launch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-hdmi-respond.sh',
|
||||||
|
'/etc/flowercore/divoom-tv.env',
|
||||||
|
]:
|
||||||
|
ensure => absent,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
case $facts['os']['family'] {
|
||||||
|
'Debian': {}
|
||||||
|
default: { fail("profile::pi::service::divoom_tv only supports Debian-family OS, got ${facts['os']['family']}") }
|
||||||
|
}
|
||||||
|
|
||||||
|
package { ['cage', 'libgbm1', 'libdrm2', 'libxkbcommon0', 'fonts-dejavu-core']:
|
||||||
|
ensure => installed,
|
||||||
|
}
|
||||||
|
|
||||||
|
group { $group:
|
||||||
|
ensure => present,
|
||||||
|
system => true,
|
||||||
|
}
|
||||||
|
|
||||||
|
user { $user:
|
||||||
|
ensure => present,
|
||||||
|
system => true,
|
||||||
|
gid => $group,
|
||||||
|
home => $state_dir,
|
||||||
|
managehome => false,
|
||||||
|
shell => '/usr/sbin/nologin',
|
||||||
|
require => Group[$group],
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [$install_dir, $state_dir, $log_dir, '/etc/flowercore']:
|
||||||
|
ensure => directory,
|
||||||
|
owner => $user,
|
||||||
|
group => $group,
|
||||||
|
mode => '0755',
|
||||||
|
}
|
||||||
|
|
||||||
|
file { '/etc/flowercore/divoom-tv.env':
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => "FC_DIVOOM_TV_PRESENTATION_MODE=${presentation_mode}\nFC_DIVOOM_TV_START_SCENE=${startup_scene}\nFC_DIVOOM_TV_REDUCED_MOTION=${reduced_motion}\n",
|
||||||
|
require => File['/etc/flowercore'],
|
||||||
|
}
|
||||||
|
|
||||||
|
$script_map = {
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-prelaunch.sh' => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-prelaunch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-launch.sh' => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-launch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-hdmi-respond.sh' => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-hdmi-respond.sh',
|
||||||
|
}
|
||||||
|
|
||||||
|
$script_map.each |$dest, $src| {
|
||||||
|
file { $dest:
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0755',
|
||||||
|
source => "puppet:///modules/${src}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit_map = {
|
||||||
|
"/etc/systemd/system/${service_name}.service" => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv.service',
|
||||||
|
"/etc/systemd/system/${service_name}-hdmi.service" => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-hdmi.service',
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit_map.each |$dest, $src| {
|
||||||
|
file { $dest:
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
source => "puppet:///modules/${src}",
|
||||||
|
notify => Exec['fc-divoom-tv-systemd-reload'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file { '/etc/udev/rules.d/99-flowercore-divoom-tv-hdmi.rules':
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
source => 'puppet:///modules/profile/pi/fc_divoom_tv/99-flowercore-divoom-tv-hdmi.rules',
|
||||||
|
notify => Exec['fc-divoom-tv-udev-reload'],
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-tv-systemd-reload':
|
||||||
|
command => '/usr/bin/systemctl daemon-reload',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-tv-udev-reload':
|
||||||
|
command => '/usr/bin/udevadm control --reload-rules',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
|
||||||
|
service { $service_name:
|
||||||
|
ensure => $service_ensure,
|
||||||
|
enable => $service_enabled,
|
||||||
|
require => [
|
||||||
|
File["/etc/systemd/system/${service_name}.service"],
|
||||||
|
File['/etc/flowercore/divoom-tv.env'],
|
||||||
|
File['/usr/local/bin/flowercore-divoom-tv-prelaunch.sh'],
|
||||||
|
File['/usr/local/bin/flowercore-divoom-tv-launch.sh'],
|
||||||
|
Exec['fc-divoom-tv-systemd-reload'],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
systemctl restart flowercore-divoom-tv.service
|
||||||
25
apps/fc-divoom-tv-pi/scripts/flowercore-divoom-tv-launch.sh
Normal file
25
apps/fc-divoom-tv-pi/scripts/flowercore-divoom-tv-launch.sh
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP_BIN="${FC_DIVOOM_TV_BIN:-/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv}"
|
||||||
|
STATE_DIR="${FC_DIVOOM_TV_STATE_DIR:-/var/lib/fc-divoom-tv}"
|
||||||
|
LOG_DIR="${FC_DIVOOM_TV_LOG_DIR:-/var/log/fc-divoom-tv}"
|
||||||
|
PRESENTATION_MODE="${FC_DIVOOM_TV_PRESENTATION_MODE:-PillarboxSquare}"
|
||||||
|
START_SCENE="${FC_DIVOOM_TV_START_SCENE:-bluejay-clock}"
|
||||||
|
REDUCED_MOTION="${FC_DIVOOM_TV_REDUCED_MOTION:-false}"
|
||||||
|
|
||||||
|
COMMON_ARGS=(
|
||||||
|
"--target=hdmi"
|
||||||
|
"--presentation-mode=${PRESENTATION_MODE}"
|
||||||
|
"--startup-scene=${START_SCENE}"
|
||||||
|
"--reduced-motion=${REDUCED_MOTION}"
|
||||||
|
"--state-dir=${STATE_DIR}"
|
||||||
|
"--log-dir=${LOG_DIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if command -v cage >/dev/null 2>&1; then
|
||||||
|
exec cage -- "${APP_BIN}" "${COMMON_ARGS[@]}" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[$(date -Is)] cage not found; launching FlowerCore.Divoom.Tv directly" >&2
|
||||||
|
exec "${APP_BIN}" "${COMMON_ARGS[@]}" "$@"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP_BIN="${FC_DIVOOM_TV_BIN:-/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv}"
|
||||||
|
STATE_DIR="${FC_DIVOOM_TV_STATE_DIR:-/var/lib/fc-divoom-tv}"
|
||||||
|
LOG_DIR="${FC_DIVOOM_TV_LOG_DIR:-/var/log/fc-divoom-tv}"
|
||||||
|
|
||||||
|
mkdir -p "${STATE_DIR}" "${LOG_DIR}"
|
||||||
|
|
||||||
|
if [[ ! -x "${APP_BIN}" ]]; then
|
||||||
|
echo "[$(date -Is)] missing executable ${APP_BIN}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d /sys/class/drm ]] && ! find /sys/class/drm -maxdepth 1 -name 'card*-HDMI-A-*' -print -quit | grep -q .; then
|
||||||
|
echo "[$(date -Is)] no HDMI connector visible yet; continuing so the app can wait for display" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v cage >/dev/null 2>&1; then
|
||||||
|
echo "[$(date -Is)] cage available for fullscreen Wayland launch"
|
||||||
|
else
|
||||||
|
echo "[$(date -Is)] cage not installed; direct launch fallback will be used" >&2
|
||||||
|
fi
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Settle DRM for 2s before restarting the fullscreen Avalonia renderer.
|
||||||
|
SUBSYSTEM=="drm", KERNEL=="card?-HDMI-A-?", ACTION=="change", RUN+="/usr/bin/systemctl start flowercore-divoom-tv-hdmi.service"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Divoom TV HDMI hotplug responder
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/flowercore-divoom-tv-hdmi-respond.sh
|
||||||
40
apps/fc-divoom-tv-pi/systemd/flowercore-divoom-tv.service
Normal file
40
apps/fc-divoom-tv-pi/systemd/flowercore-divoom-tv.service
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Divoom TV HDMI Renderer (Avalonia fullscreen)
|
||||||
|
Documentation=https://github.com/astoltz/FlowerCore.Notes/blob/master/docs/standards/divoom-tv-hdmi-multitarget-render-substrate.md
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target systemd-user-sessions.service
|
||||||
|
ConditionPathExists=/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=fc-divoom-tv
|
||||||
|
Group=fc-divoom-tv
|
||||||
|
WorkingDirectory=/opt/flowercore/divoom-tv
|
||||||
|
EnvironmentFile=-/etc/flowercore/divoom-tv.env
|
||||||
|
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
Environment=XDG_RUNTIME_DIR=/run/fc-divoom-tv
|
||||||
|
RuntimeDirectory=fc-divoom-tv
|
||||||
|
RuntimeDirectoryMode=0700
|
||||||
|
ExecStartPre=/usr/local/bin/flowercore-divoom-tv-prelaunch.sh
|
||||||
|
ExecStart=/usr/local/bin/flowercore-divoom-tv-launch.sh
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10s
|
||||||
|
StartLimitBurst=5
|
||||||
|
StartLimitIntervalSec=300s
|
||||||
|
MemoryMax=2G
|
||||||
|
MemoryHigh=1500M
|
||||||
|
PrivateTmp=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/lib/fc-divoom-tv /var/log/fc-divoom-tv /run/fc-divoom-tv
|
||||||
|
TTYPath=/dev/tty1
|
||||||
|
StandardInput=tty
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
TTYReset=yes
|
||||||
|
TTYVHangup=yes
|
||||||
|
TTYVTDisallocate=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical.target
|
||||||
169
apps/fc-library/fc-library.yaml
Normal file
169
apps/fc-library/fc-library.yaml
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# FlowerCore.Library.Web GitOps adoption manifest.
|
||||||
|
#
|
||||||
|
# Authored from the already-live fc-library resources on 2026-06-04.
|
||||||
|
# Keep the live image tag, Service ClusterIP, and PVC volumeName unchanged so
|
||||||
|
# ArgoCD adopts in place instead of replacing the workload or data volume.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: library-web-data
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
volumeName: pvc-2690bae2-4ee0-417a-b95f-50ec5c632b63
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: library-web
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
progressDeadlineSeconds: 600
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
prometheus.io/path: /metrics/prometheus
|
||||||
|
prometheus.io/port: "5000"
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: library-web-config
|
||||||
|
image: localhost/fc-library-web:v20260602-library-owned-deploy-fix1
|
||||||
|
imagePullPolicy: Never
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 5
|
||||||
|
name: library-web
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 6
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 5
|
||||||
|
resources: {}
|
||||||
|
terminationMessagePath: /dev/termination-log
|
||||||
|
terminationMessagePolicy: File
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /data
|
||||||
|
name: data
|
||||||
|
dnsPolicy: ClusterFirst
|
||||||
|
restartPolicy: Always
|
||||||
|
schedulerName: default-scheduler
|
||||||
|
securityContext: {}
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: library-web-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: library-web
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.43.179.63
|
||||||
|
clusterIPs:
|
||||||
|
- 10.43.179.63
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: library-web-tls
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web-tls
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
dnsNames:
|
||||||
|
- library.iamworkin.lan
|
||||||
|
issuerRef:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: step-ca-acme
|
||||||
|
secretName: library-web-tls
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: library-web
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`library.iamworkin.lan`)
|
||||||
|
services:
|
||||||
|
- name: library-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: library-web-tls
|
||||||
170
apps/fc-retail/fc-retail.yaml
Normal file
170
apps/fc-retail/fc-retail.yaml
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# FlowerCore.Retail.Web GitOps adoption manifest.
|
||||||
|
#
|
||||||
|
# Authored from the already-live fc-retail resources on 2026-06-04.
|
||||||
|
# Keep the live image tag, Service ClusterIP, and PVC volumeName unchanged so
|
||||||
|
# ArgoCD adopts in place instead of replacing the workload or data volume.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: retail-web-data
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
volumeName: pvc-3d40b336-eab4-41b3-812c-d5e9413ce0ab
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: retail-web
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
progressDeadlineSeconds: 600
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
kubectl.kubernetes.io/restartedAt: "2026-06-02T01:34:08-05:00"
|
||||||
|
prometheus.io/path: /metrics/prometheus
|
||||||
|
prometheus.io/port: "5000"
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: retail-web-config
|
||||||
|
image: localhost/fc-retail-web:v20260602-retail-owned-deploy-fix5
|
||||||
|
imagePullPolicy: Never
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 5
|
||||||
|
name: retail-web
|
||||||
|
ports:
|
||||||
|
- containerPort: 5000
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 6
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 5000
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 5
|
||||||
|
resources: {}
|
||||||
|
terminationMessagePath: /dev/termination-log
|
||||||
|
terminationMessagePolicy: File
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /data
|
||||||
|
name: data
|
||||||
|
dnsPolicy: ClusterFirst
|
||||||
|
restartPolicy: Always
|
||||||
|
schedulerName: default-scheduler
|
||||||
|
securityContext: {}
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: retail-web-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: retail-web
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.43.239.8
|
||||||
|
clusterIPs:
|
||||||
|
- 10.43.239.8
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: retail-web-tls
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web-tls
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
dnsNames:
|
||||||
|
- retail.iamworkin.lan
|
||||||
|
issuerRef:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: step-ca-acme
|
||||||
|
secretName: retail-web-tls
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: retail-web
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`retail.iamworkin.lan`)
|
||||||
|
services:
|
||||||
|
- name: retail-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: retail-web-tls
|
||||||
@@ -532,7 +532,7 @@ spec:
|
|||||||
fsGroupChangePolicy: OnRootMismatch
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: localhost/fc-ttsreader-web:v20260518-sprint36-demo-finish-b132cbf
|
image: localhost/fc-ttsreader-web:v20260603-s54cx14-pr29-schema
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 5217
|
- containerPort: 5217
|
||||||
@@ -554,6 +554,8 @@ spec:
|
|||||||
value: "/data/chapter-context.db"
|
value: "/data/chapter-context.db"
|
||||||
- name: TtsReader__Jobs__Root
|
- name: TtsReader__Jobs__Root
|
||||||
value: "/data/jobs"
|
value: "/data/jobs"
|
||||||
|
- name: TtsReader__Export__LocalCasRoot
|
||||||
|
value: "/data/bundles/cas"
|
||||||
- name: TtsReader__Piper__Host
|
- name: TtsReader__Piper__Host
|
||||||
value: "10.0.57.17"
|
value: "10.0.57.17"
|
||||||
- name: TtsReader__Piper__Port
|
- name: TtsReader__Piper__Port
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ spec:
|
|||||||
nodeName: rke2-server
|
nodeName: rke2-server
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: localhost/fc-updater-web:v20260509-4162dca-authgate
|
image: localhost/fc-updater-web:v202605310029-7974fc4
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
@@ -88,6 +88,8 @@ spec:
|
|||||||
value: Faith AI Mike Edition
|
value: Faith AI Mike Edition
|
||||||
- name: FlowerCore__Updater__PublicShares__Links__0__Description
|
- name: FlowerCore__Updater__PublicShares__Links__0__Description
|
||||||
value: Private release link for Mike's Faith AI bundle.
|
value: Private release link for Mike's Faith AI bundle.
|
||||||
|
- name: FlowerCore__Audit__Sinks__Loki__Enabled
|
||||||
|
value: "false"
|
||||||
- name: FlowerCore__Updater__Auth__Bootstrap__Enabled
|
- name: FlowerCore__Updater__Auth__Bootstrap__Enabled
|
||||||
value: "true"
|
value: "true"
|
||||||
- name: FlowerCore__Updater__Auth__Bootstrap__Username
|
- name: FlowerCore__Updater__Auth__Bootstrap__Username
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -306,7 +306,7 @@ spec:
|
|||||||
# UN-PARKED 2026-05-21: Shared.Pos #5 fixed the non-root setup-dotnet path
|
# UN-PARKED 2026-05-21: Shared.Pos #5 fixed the non-root setup-dotnet path
|
||||||
# (DOTNET_INSTALL_DIR step-scoped). Sprint 30 Cl-8 capacity Q-CI-52: raised
|
# (DOTNET_INSTALL_DIR step-scoped). Sprint 30 Cl-8 capacity Q-CI-52: raised
|
||||||
# to replicas: 2 to absorb top-8 burst load per substrate-recommended default.
|
# to replicas: 2 to absorb top-8 burst load per substrate-recommended default.
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-sharedpos
|
app.kubernetes.io/name: github-runner-sharedpos
|
||||||
@@ -397,7 +397,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -448,7 +448,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: puppet
|
flowercore.io/runner-repo: puppet
|
||||||
flowercore.io/github-repo: FlowerCore.Puppet
|
flowercore.io/github-repo: FlowerCore.Puppet
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-puppet
|
app.kubernetes.io/name: github-runner-puppet
|
||||||
@@ -533,7 +533,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -580,7 +580,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: signage
|
flowercore.io/runner-repo: signage
|
||||||
flowercore.io/github-repo: FlowerCore.Signage
|
flowercore.io/github-repo: FlowerCore.Signage
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-signage
|
app.kubernetes.io/name: github-runner-signage
|
||||||
@@ -665,7 +665,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -712,7 +712,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: dms
|
flowercore.io/runner-repo: dms
|
||||||
flowercore.io/github-repo: FlowerCore.DMS
|
flowercore.io/github-repo: FlowerCore.DMS
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-dms
|
app.kubernetes.io/name: github-runner-dms
|
||||||
@@ -797,7 +797,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -844,7 +844,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: telephony
|
flowercore.io/runner-repo: telephony
|
||||||
flowercore.io/github-repo: FlowerCore.Telephony
|
flowercore.io/github-repo: FlowerCore.Telephony
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-telephony
|
app.kubernetes.io/name: github-runner-telephony
|
||||||
@@ -929,7 +929,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -979,7 +979,7 @@ spec:
|
|||||||
# Sprint 33 morning-routine (2026-05-25): bumped 2 → 3 because help-screenshots
|
# Sprint 33 morning-routine (2026-05-25): bumped 2 → 3 because help-screenshots
|
||||||
# AAT job holds a runner 30+ min, causing head-of-line blocking on parallel PRs.
|
# AAT job holds a runner 30+ min, causing head-of-line blocking on parallel PRs.
|
||||||
# 12 runs in trailing 5d.
|
# 12 runs in trailing 5d.
|
||||||
replicas: 3
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-print-web
|
app.kubernetes.io/name: github-runner-print-web
|
||||||
@@ -1064,7 +1064,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -1111,7 +1111,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: chat
|
flowercore.io/runner-repo: chat
|
||||||
flowercore.io/github-repo: FlowerCore.Chat
|
flowercore.io/github-repo: FlowerCore.Chat
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-chat
|
app.kubernetes.io/name: github-runner-chat
|
||||||
@@ -1196,7 +1196,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -1243,7 +1243,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: mysql
|
flowercore.io/runner-repo: mysql
|
||||||
flowercore.io/github-repo: FlowerCore.MySQL
|
flowercore.io/github-repo: FlowerCore.MySQL
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-mysql
|
app.kubernetes.io/name: github-runner-mysql
|
||||||
@@ -1328,7 +1328,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -1375,7 +1375,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: kiosk-linux
|
flowercore.io/runner-repo: kiosk-linux
|
||||||
flowercore.io/github-repo: FlowerCore.Kiosk.Linux
|
flowercore.io/github-repo: FlowerCore.Kiosk.Linux
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-kiosk-linux
|
app.kubernetes.io/name: github-runner-kiosk-linux
|
||||||
@@ -1460,7 +1460,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -1509,7 +1509,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: marquee
|
flowercore.io/runner-repo: marquee
|
||||||
flowercore.io/github-repo: FlowerCore.Marquee
|
flowercore.io/github-repo: FlowerCore.Marquee
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-marquee
|
app.kubernetes.io/name: github-runner-marquee
|
||||||
@@ -1594,7 +1594,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -1643,7 +1643,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: tts-reader
|
flowercore.io/runner-repo: tts-reader
|
||||||
flowercore.io/github-repo: FlowerCore.TtsReader
|
flowercore.io/github-repo: FlowerCore.TtsReader
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-tts-reader
|
app.kubernetes.io/name: github-runner-tts-reader
|
||||||
@@ -1732,7 +1732,7 @@ spec:
|
|||||||
# symptoms surfaced. 8Gi gives ~30% headroom over peak observed.
|
# symptoms surfaced. 8Gi gives ~30% headroom over peak observed.
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "2Gi"
|
memory: "2Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -1867,7 +1867,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2001,7 +2001,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2135,7 +2135,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2269,7 +2269,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2317,7 +2317,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: remote-desktop
|
flowercore.io/runner-repo: remote-desktop
|
||||||
flowercore.io/github-repo: FlowerCore.RemoteDesktop
|
flowercore.io/github-repo: FlowerCore.RemoteDesktop
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-remote-desktop
|
app.kubernetes.io/name: github-runner-remote-desktop
|
||||||
@@ -2402,7 +2402,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2536,7 +2536,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2584,7 +2584,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: distribution
|
flowercore.io/runner-repo: distribution
|
||||||
flowercore.io/github-repo: FlowerCore.Distribution
|
flowercore.io/github-repo: FlowerCore.Distribution
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-distribution
|
app.kubernetes.io/name: github-runner-distribution
|
||||||
@@ -2669,7 +2669,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2717,7 +2717,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: scoreboard
|
flowercore.io/runner-repo: scoreboard
|
||||||
flowercore.io/github-repo: FlowerCore.Scoreboard
|
flowercore.io/github-repo: FlowerCore.Scoreboard
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-scoreboard
|
app.kubernetes.io/name: github-runner-scoreboard
|
||||||
@@ -2802,7 +2802,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2850,7 +2850,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: segment-display
|
flowercore.io/runner-repo: segment-display
|
||||||
flowercore.io/github-repo: FlowerCore.SegmentDisplay
|
flowercore.io/github-repo: FlowerCore.SegmentDisplay
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-segment-display
|
app.kubernetes.io/name: github-runner-segment-display
|
||||||
@@ -2935,7 +2935,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2983,7 +2983,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: signage-contracts
|
flowercore.io/runner-repo: signage-contracts
|
||||||
flowercore.io/github-repo: FlowerCore.Signage.Contracts
|
flowercore.io/github-repo: FlowerCore.Signage.Contracts
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-signage-contracts
|
app.kubernetes.io/name: github-runner-signage-contracts
|
||||||
@@ -3068,7 +3068,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3116,7 +3116,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: signal-control
|
flowercore.io/runner-repo: signal-control
|
||||||
flowercore.io/github-repo: FlowerCore.SignalControl
|
flowercore.io/github-repo: FlowerCore.SignalControl
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-signal-control
|
app.kubernetes.io/name: github-runner-signal-control
|
||||||
@@ -3201,7 +3201,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3335,7 +3335,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3469,7 +3469,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3603,7 +3603,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3737,7 +3737,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3871,7 +3871,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3919,7 +3919,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: pimanager
|
flowercore.io/runner-repo: pimanager
|
||||||
flowercore.io/github-repo: FlowerCore.PiManager
|
flowercore.io/github-repo: FlowerCore.PiManager
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-pimanager
|
app.kubernetes.io/name: github-runner-pimanager
|
||||||
@@ -4004,7 +4004,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -4053,7 +4053,7 @@ metadata:
|
|||||||
flowercore.io/runner-repo: updater
|
flowercore.io/runner-repo: updater
|
||||||
flowercore.io/github-repo: FlowerCore.Updater
|
flowercore.io/github-repo: FlowerCore.Updater
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-updater
|
app.kubernetes.io/name: github-runner-updater
|
||||||
@@ -4138,7 +4138,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -4189,7 +4189,9 @@ metadata:
|
|||||||
flowercore.io/runner-repo: device-management
|
flowercore.io/runner-repo: device-management
|
||||||
flowercore.io/github-repo: FlowerCore.DeviceManagement
|
flowercore.io/github-repo: FlowerCore.DeviceManagement
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
# Single replica until cluster CPU pressure resolves; the fleet-wide
|
||||||
|
# request right-sizing pass is queued for a future sweep.
|
||||||
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-device-management
|
app.kubernetes.io/name: github-runner-device-management
|
||||||
@@ -4273,8 +4275,11 @@ spec:
|
|||||||
- name: RUN_AS_ROOT
|
- name: RUN_AS_ROOT
|
||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
|
# Reduced from 500m → 100m 2026-05-26 because cluster CPU
|
||||||
|
# requests were at 99% across all 3 nodes; idle runners use ~1m.
|
||||||
|
# Burst headroom preserved by limits.cpu: 2000m.
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -4323,7 +4328,9 @@ metadata:
|
|||||||
flowercore.io/runner-repo: aistation-linux
|
flowercore.io/runner-repo: aistation-linux
|
||||||
flowercore.io/github-repo: FlowerCore.AiStation.Linux
|
flowercore.io/github-repo: FlowerCore.AiStation.Linux
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
# Single replica until cluster CPU pressure resolves; the fleet-wide
|
||||||
|
# request right-sizing pass is queued for a future sweep.
|
||||||
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-aistation-linux
|
app.kubernetes.io/name: github-runner-aistation-linux
|
||||||
@@ -4408,7 +4415,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -4456,7 +4463,9 @@ metadata:
|
|||||||
flowercore.io/runner-repo: worldbuilder
|
flowercore.io/runner-repo: worldbuilder
|
||||||
flowercore.io/github-repo: FlowerCore.WorldBuilder
|
flowercore.io/github-repo: FlowerCore.WorldBuilder
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
# Single replica until cluster CPU pressure resolves; the fleet-wide
|
||||||
|
# request right-sizing pass is queued for a future sweep.
|
||||||
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-worldbuilder
|
app.kubernetes.io/name: github-runner-worldbuilder
|
||||||
@@ -4541,7 +4550,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "500m"
|
cpu: "100m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: intranet-web
|
- name: intranet-web
|
||||||
image: localhost/fc-intranet-web:v20260508-brochure-w1
|
image: localhost/fc-intranet-web:v20260531-ttsreader-bridge
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 5300
|
- containerPort: 5300
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ spec:
|
|||||||
- name: web
|
- name: web
|
||||||
# Placeholder tag — bump to the image you built + imported to ALL
|
# Placeholder tag — bump to the image you built + imported to ALL
|
||||||
# RKE2 nodes via scripts/deploy-knowledge.sh before applying.
|
# RKE2 nodes via scripts/deploy-knowledge.sh before applying.
|
||||||
image: localhost/fc-knowledge-web:v20260429232635
|
image: localhost/fc-knowledge-web:v20260603-oidc-authentik-auditfix
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
@@ -123,6 +123,25 @@ spec:
|
|||||||
value: "Production"
|
value: "Production"
|
||||||
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||||
value: "false"
|
value: "false"
|
||||||
|
# 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: "false"
|
||||||
|
- name: FlowerCore__Auth__Oidc__Enabled
|
||||||
|
value: "true"
|
||||||
|
- name: FlowerCore__Auth__Oidc__Authority
|
||||||
|
value: "https://id.iamworkin.lan/application/o/knowledge/"
|
||||||
|
- name: FlowerCore__Auth__Oidc__Audience
|
||||||
|
value: "knowledge"
|
||||||
|
- name: FlowerCore__Auth__Oidc__ClientId
|
||||||
|
value: "knowledge"
|
||||||
|
- name: FlowerCore__Auth__Oidc__ClientSecret
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: knowledge-oidc-client
|
||||||
|
key: client_secret
|
||||||
|
optional: true
|
||||||
# Vector-store directory + embedding model + edition profile dir.
|
# Vector-store directory + embedding model + edition profile dir.
|
||||||
# Profile JSON is baked into the image at /home/app/editions via the
|
# Profile JSON is baked into the image at /home/app/editions via the
|
||||||
# csproj Content-link from FlowerCore.Common/editions/.
|
# csproj Content-link from FlowerCore.Common/editions/.
|
||||||
@@ -134,6 +153,8 @@ spec:
|
|||||||
value: "5"
|
value: "5"
|
||||||
- name: Knowledge__MaxLimit
|
- name: Knowledge__MaxLimit
|
||||||
value: "50"
|
value: "50"
|
||||||
|
- name: Knowledge__Federation__DatabasePath
|
||||||
|
value: "/data/vector-stores/knowledge-federation.db"
|
||||||
- name: FlowerCore__Editions__ProfileDirectory
|
- name: FlowerCore__Editions__ProfileDirectory
|
||||||
value: "/home/app/editions"
|
value: "/home/app/editions"
|
||||||
# Embed via edge1 Pi 5 + AI HAT+ (10.0.57.17:11434). Cluster
|
# Embed via edge1 Pi 5 + AI HAT+ (10.0.57.17:11434). Cluster
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ metadata:
|
|||||||
role: github-actions-runner
|
role: github-actions-runner
|
||||||
flowercore.io/managed-by: bluejay-infra
|
flowercore.io/managed-by: bluejay-infra
|
||||||
spec:
|
spec:
|
||||||
runStrategy: Always
|
runStrategy: Halted
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -207,20 +207,13 @@ spec:
|
|||||||
- port: 993
|
- port: 993
|
||||||
targetPort: 993
|
targetPort: 993
|
||||||
name: imaps
|
name: imaps
|
||||||
---
|
# --- mail-tls Certificate REMOVED 2026-06-01 ---
|
||||||
# TLS Certificate via cert-manager
|
# mail-tls is now managed OUTSIDE cert-manager: issued from step-ca's JWK 'admin'
|
||||||
apiVersion: cert-manager.io/v1
|
# provisioner and auto-renewed by a systemd timer on noc1 (step ca renew), which
|
||||||
kind: Certificate
|
# writes the mail-tls secret directly. step-ca-acme only has an HTTP-01 (Traefik)
|
||||||
metadata:
|
# solver, but mail.iamworkin.lan must resolve to the dedicated MetalLB IP 10.0.56.202
|
||||||
name: mail-tls
|
# (SMTP/IMAP), so HTTP-01 cannot validate. Do NOT re-add a cert-manager Certificate
|
||||||
namespace: mail
|
# here unless a DNS-01 solver is deployed for step-ca-acme.
|
||||||
spec:
|
|
||||||
secretName: mail-tls
|
|
||||||
issuerRef:
|
|
||||||
name: step-ca-acme
|
|
||||||
kind: ClusterIssuer
|
|
||||||
dnsNames:
|
|
||||||
- mail.iamworkin.lan
|
|
||||||
---
|
---
|
||||||
# Traefik IngressRoute - Webmail placeholder
|
# Traefik IngressRoute - Webmail placeholder
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ data:
|
|||||||
service: "pimanager"
|
service: "pimanager"
|
||||||
vlan: "home"
|
vlan: "home"
|
||||||
device: "pi4-ezconnect"
|
device: "pi4-ezconnect"
|
||||||
- targets: ["10.0.58.113:5100"]
|
- targets: ["10.0.58.113:5200"]
|
||||||
labels:
|
labels:
|
||||||
instance: "pirelay"
|
instance: "pirelay"
|
||||||
service: "pimanager"
|
service: "pimanager"
|
||||||
@@ -479,15 +479,17 @@ data:
|
|||||||
- "https://gitea.iamworkin.lan/"
|
- "https://gitea.iamworkin.lan/"
|
||||||
- "https://argocd.iamworkin.lan/"
|
- "https://argocd.iamworkin.lan/"
|
||||||
- "https://intranet.iamworkin.lan/"
|
- "https://intranet.iamworkin.lan/"
|
||||||
- "https://signage.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://kiosk.iamworkin.lan/"
|
||||||
- "https://media.iamworkin.lan/"
|
- "https://media.iamworkin.lan/"
|
||||||
- "https://mysql.iamworkin.lan/"
|
- "https://mysql.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||||
- "https://php.iamworkin.lan/"
|
- "https://php.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
||||||
|
- "https://dns.iamworkin.lan/"
|
||||||
- "https://zabbix.iamworkin.lan/"
|
- "https://zabbix.iamworkin.lan/"
|
||||||
|
- "https://flowercore.iamworkin.lan/healthz"
|
||||||
- "https://desktop.iamworkin.lan/"
|
- "https://desktop.iamworkin.lan/"
|
||||||
- "https://print.iamworkin.lan/"
|
- "https://print.iamworkin.lan/"
|
||||||
- "https://dns.iamworkin.lan/"
|
|
||||||
- "https://chat.iamworkin.lan/"
|
- "https://chat.iamworkin.lan/"
|
||||||
- "https://dist.iamworkin.lan/"
|
- "https://dist.iamworkin.lan/"
|
||||||
- "https://dms.iamworkin.lan/"
|
- "https://dms.iamworkin.lan/"
|
||||||
@@ -496,9 +498,15 @@ data:
|
|||||||
- "https://presentations.iamworkin.lan/"
|
- "https://presentations.iamworkin.lan/"
|
||||||
- "https://retail.iamworkin.lan/"
|
- "https://retail.iamworkin.lan/"
|
||||||
- "https://ttsreader.iamworkin.lan/"
|
- "https://ttsreader.iamworkin.lan/"
|
||||||
|
- "https://updates.iamworkin.lan/api/v1/manifests/_schema"
|
||||||
# Explicit healthcheck paths
|
# Explicit healthcheck paths
|
||||||
- "https://fc-llm-bridge.iamworkin.lan/healthz"
|
- "https://fc-llm-bridge.iamworkin.lan/healthz"
|
||||||
- "https://acme.iamworkin.lan/health"
|
- "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
|
# NOTE: services intentionally NOT in this probe surface
|
||||||
# - grafana.iamworkin.lan: every endpoint (incl. /api/health
|
# - grafana.iamworkin.lan: every endpoint (incl. /api/health
|
||||||
# and /login) returns 401 behind Traefik basic-auth.
|
# and /login) returns 401 behind Traefik basic-auth.
|
||||||
@@ -907,11 +915,14 @@ data:
|
|||||||
# for: 30m absorbs sleep cycles. The EcoTank sleeps after ~5 min
|
# for: 30m absorbs sleep cycles. The EcoTank sleeps after ~5 min
|
||||||
# of idle and SNMP times out, so 5m for: would page nightly. A
|
# of idle and SNMP times out, so 5m for: would page nightly. A
|
||||||
# genuine printer outage (jam, disconnected) lasts well over 30m.
|
# 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
|
- alert: EpsonPrinterDown
|
||||||
expr: up{job="snmp-printer"} == 0
|
expr: (max_over_time(up{job="snmp-printer"}[35m]) == bool 0) == 1 and (hour() >= 13 or hour() < 1)
|
||||||
for: 30m
|
for: 30m
|
||||||
labels:
|
labels:
|
||||||
severity: warning
|
severity: warning
|
||||||
|
alert_channel: irc
|
||||||
annotations:
|
annotations:
|
||||||
summary: "Epson ET-3750 SNMP unreachable for >30m (likely actual fault, not sleep)"
|
summary: "Epson ET-3750 SNMP unreachable for >30m (likely actual fault, not sleep)"
|
||||||
|
|
||||||
@@ -1020,7 +1031,9 @@ data:
|
|||||||
- name: kubernetes-state
|
- name: kubernetes-state
|
||||||
rules:
|
rules:
|
||||||
- alert: KubeContainerRestartingFrequently
|
- alert: KubeContainerRestartingFrequently
|
||||||
expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
|
# 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"}[1h]) > 5
|
||||||
for: 15m
|
for: 15m
|
||||||
labels:
|
labels:
|
||||||
severity: warning
|
severity: warning
|
||||||
@@ -1029,7 +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."
|
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
|
- alert: KubeContainerCrashLooping
|
||||||
expr: increase(kube_pod_container_status_restarts_total[15m]) > 3
|
# 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
|
for: 5m
|
||||||
labels:
|
labels:
|
||||||
severity: critical
|
severity: critical
|
||||||
@@ -1057,7 +1072,8 @@ 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."
|
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
|
- alert: KubeDeploymentReplicasMismatch
|
||||||
expr: kube_deployment_spec_replicas != kube_deployment_status_replicas_available
|
# 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
|
for: 15m
|
||||||
labels:
|
labels:
|
||||||
severity: warning
|
severity: warning
|
||||||
@@ -3636,6 +3652,38 @@ data:
|
|||||||
relativeTimeRange: {from: 120, to: 0}
|
relativeTimeRange: {from: 120, to: 0}
|
||||||
datasourceUid: __expr__
|
datasourceUid: __expr__
|
||||||
model: {type: threshold, expression: B, conditions: [{evaluator: {params: [600], type: gt}}], refId: C}
|
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
|
- orgId: 1
|
||||||
name: CI Runners
|
name: CI Runners
|
||||||
folder: CI Alerts
|
folder: CI Alerts
|
||||||
|
|||||||
206
tests/bluejay-infra-lint/DivoomPiDeployArtifactTests.cs
Normal file
206
tests/bluejay-infra-lint/DivoomPiDeployArtifactTests.cs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace BluejayInfraLint.Tests;
|
||||||
|
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class DivoomPiDeployArtifactTests
|
||||||
|
{
|
||||||
|
private static readonly string Root = FindRepoRoot();
|
||||||
|
private static readonly string DmRoot = Path.Combine(Root, "apps", "fc-divoom-dm-pi-device");
|
||||||
|
private static readonly string TvRoot = Path.Combine(Root, "apps", "fc-divoom-tv-pi");
|
||||||
|
|
||||||
|
public static TheoryData<string> DmRequiredArtifacts => new()
|
||||||
|
{
|
||||||
|
"README.md",
|
||||||
|
"hiera/edge2-divoom-dm-device.overlay.yaml",
|
||||||
|
"puppet/profile/pi/service/divoom_dm_device.pp",
|
||||||
|
"puppet/templates/divoom-device-registration.json.epp",
|
||||||
|
"puppet/templates/flowercore-divoom-dm-agent.service.epp",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static TheoryData<string> TvRequiredArtifacts => new()
|
||||||
|
{
|
||||||
|
"README.md",
|
||||||
|
"hiera/example-divoom-tv-pi.iamworkin.lan.yaml",
|
||||||
|
"puppet/profile/pi/service/divoom_tv.pp",
|
||||||
|
"systemd/flowercore-divoom-tv.service",
|
||||||
|
"systemd/flowercore-divoom-tv-hdmi.service",
|
||||||
|
"systemd/99-flowercore-divoom-tv-hdmi.rules",
|
||||||
|
"scripts/flowercore-divoom-tv-prelaunch.sh",
|
||||||
|
"scripts/flowercore-divoom-tv-launch.sh",
|
||||||
|
"scripts/flowercore-divoom-tv-hdmi-respond.sh",
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(DmRequiredArtifacts))]
|
||||||
|
public void DmDeviceArtifacts_ArePresent(string relativePath)
|
||||||
|
{
|
||||||
|
File.Exists(Path.Combine(DmRoot, relativePath.Replace('/', Path.DirectorySeparatorChar))).Should().BeTrue(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TvRequiredArtifacts))]
|
||||||
|
public void TvPiArtifacts_ArePresent(string relativePath)
|
||||||
|
{
|
||||||
|
File.Exists(Path.Combine(TvRoot, relativePath.Replace('/', Path.DirectorySeparatorChar))).Should().BeTrue(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DmDeviceReadme_DeclaresPuppetSystemdNotKubernetes()
|
||||||
|
{
|
||||||
|
var readme = ReadDm("README.md");
|
||||||
|
|
||||||
|
readme.Should().Contain("not a Kubernetes application");
|
||||||
|
readme.Should().Contain("profile::pi::service::divoom");
|
||||||
|
readme.Should().Contain("no K8s surface");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DmHieraOverlay_PreservesExistingEdge2DivoomService()
|
||||||
|
{
|
||||||
|
var hiera = ReadDm("hiera/edge2-divoom-dm-device.overlay.yaml");
|
||||||
|
|
||||||
|
hiera.Should().Contain("fc-pimanager:");
|
||||||
|
hiera.Should().Contain("fc-divoom:");
|
||||||
|
hiera.Should().Contain("enabled: true");
|
||||||
|
hiera.Should().Contain("profile::pi::service::divoom_dm_device::service_enabled: false");
|
||||||
|
hiera.Should().Contain("profile::pi::service::divoom_dm_device::service_ensure: 'stopped'");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DmPuppetProfile_DefaultsToStoppedDisabledService()
|
||||||
|
{
|
||||||
|
var profile = ReadDm("puppet/profile/pi/service/divoom_dm_device.pp");
|
||||||
|
|
||||||
|
profile.Should().Contain("Boolean $service_enabled = false");
|
||||||
|
profile.Should().Contain("Enum['running', 'stopped'] $service_ensure = 'stopped'");
|
||||||
|
profile.Should().Contain("service { $service_name:");
|
||||||
|
profile.Should().Contain("ensure => $service_ensure");
|
||||||
|
profile.Should().Contain("enable => $service_enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DmPuppetProfile_DoesNotManageLiveDivoomWebUnit()
|
||||||
|
{
|
||||||
|
var profile = ReadDm("puppet/profile/pi/service/divoom_dm_device.pp");
|
||||||
|
|
||||||
|
profile.Should().NotContain("Service['flowercore-divoom.service']");
|
||||||
|
profile.Should().NotContain("service { 'flowercore-divoom.service'");
|
||||||
|
profile.Should().NotContain("notify => Service");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DmAgentUnit_IsSeparateAndGatedByExistingWrappers()
|
||||||
|
{
|
||||||
|
var unit = ReadDm("puppet/templates/flowercore-divoom-dm-agent.service.epp");
|
||||||
|
|
||||||
|
unit.Should().Contain("ConditionPathExists=<%= $divoom_install_dir %>/bt-link.sh");
|
||||||
|
unit.Should().Contain("ConditionPathExists=<%= $divoom_install_dir %>/bt-reset.sh");
|
||||||
|
unit.Should().Contain("ConditionPathExists=<%= $divoom_install_dir %>/audio-link.sh");
|
||||||
|
unit.Should().Contain("ExecStart=<%= $agent_binary_path %> --mode=Pi");
|
||||||
|
unit.Should().NotContain("flowercore-divoom.service");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DmRegistration_CarriesRenderProofAndSafetyPolicy()
|
||||||
|
{
|
||||||
|
var registration = ReadDm("puppet/templates/divoom-device-registration.json.epp");
|
||||||
|
|
||||||
|
registration.Should().Contain("\"candidateChannels\": <%= $bt_channels_json %>");
|
||||||
|
registration.Should().Contain("\"deviceInfoIsRenderProof\": false");
|
||||||
|
registration.Should().Contain("\"visibleRenderProofRequired\": <%= $visible_render_proof_required %>");
|
||||||
|
registration.Should().Contain("\"preserveExistingService\": \"flowercore-divoom.service\"");
|
||||||
|
registration.Should().Contain("\"doNotEnableFmRadio\": true");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TvService_UsesAvaloniaHdmiSafetyGates()
|
||||||
|
{
|
||||||
|
var unit = ReadTv("systemd/flowercore-divoom-tv.service");
|
||||||
|
|
||||||
|
unit.Should().Contain("ConditionPathExists=/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv");
|
||||||
|
unit.Should().Contain("Environment=XDG_RUNTIME_DIR=/run/fc-divoom-tv");
|
||||||
|
unit.Should().Contain("RuntimeDirectoryMode=0700");
|
||||||
|
unit.Should().Contain("ExecStartPre=/usr/local/bin/flowercore-divoom-tv-prelaunch.sh");
|
||||||
|
unit.Should().Contain("ExecStart=/usr/local/bin/flowercore-divoom-tv-launch.sh");
|
||||||
|
unit.Should().Contain("MemoryMax=2G");
|
||||||
|
unit.Should().Contain("PrivateTmp=true");
|
||||||
|
unit.Should().NotContain("/tmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TvLauncher_PrefersCageAndFallsBackToDirectLaunch()
|
||||||
|
{
|
||||||
|
var script = ReadTv("scripts/flowercore-divoom-tv-launch.sh");
|
||||||
|
|
||||||
|
script.Should().Contain("command -v cage");
|
||||||
|
script.Should().Contain("exec cage --");
|
||||||
|
script.Should().Contain("launching FlowerCore.Divoom.Tv directly");
|
||||||
|
script.Should().Contain("--target=hdmi");
|
||||||
|
script.Should().Contain("--presentation-mode=${PRESENTATION_MODE}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TvHotplugRule_SettlesAndRestartsRenderer()
|
||||||
|
{
|
||||||
|
var rule = ReadTv("systemd/99-flowercore-divoom-tv-hdmi.rules");
|
||||||
|
var responder = ReadTv("scripts/flowercore-divoom-tv-hdmi-respond.sh");
|
||||||
|
|
||||||
|
rule.Should().Contain("KERNEL==\"card?-HDMI-A-?\"");
|
||||||
|
rule.Should().Contain("start flowercore-divoom-tv-hdmi.service");
|
||||||
|
responder.Should().Contain("sleep 2");
|
||||||
|
responder.Should().Contain("systemctl restart flowercore-divoom-tv.service");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TvPuppetProfile_InstallsCageAndStaticArtifacts()
|
||||||
|
{
|
||||||
|
var profile = ReadTv("puppet/profile/pi/service/divoom_tv.pp");
|
||||||
|
|
||||||
|
profile.Should().Contain("package { ['cage', 'libgbm1', 'libdrm2', 'libxkbcommon0', 'fonts-dejavu-core']");
|
||||||
|
profile.Should().Contain("'profile/pi/fc_divoom_tv/flowercore-divoom-tv.service'");
|
||||||
|
profile.Should().Contain("'profile/pi/fc_divoom_tv/flowercore-divoom-tv-launch.sh'");
|
||||||
|
profile.Should().Contain("profile/pi/fc_divoom_tv/99-flowercore-divoom-tv-hdmi.rules");
|
||||||
|
profile.Should().Contain("Boolean $service_enabled = false");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DivoomArtifacts_DoNotAddKubernetesWorkloads()
|
||||||
|
{
|
||||||
|
var allText = Directory.GetFiles(DmRoot, "*", SearchOption.AllDirectories)
|
||||||
|
.Concat(Directory.GetFiles(TvRoot, "*", SearchOption.AllDirectories))
|
||||||
|
.Select(File.ReadAllText);
|
||||||
|
|
||||||
|
foreach (var text in allText)
|
||||||
|
{
|
||||||
|
text.Should().NotContain("kind: Deployment");
|
||||||
|
text.Should().NotContain("kind: IngressRoute");
|
||||||
|
text.Should().NotContain("kind: Certificate");
|
||||||
|
text.Should().NotContain("kind: OnePasswordItem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadDm(string relativePath)
|
||||||
|
=> File.ReadAllText(Path.Combine(DmRoot, relativePath.Replace('/', Path.DirectorySeparatorChar)));
|
||||||
|
|
||||||
|
private static string ReadTv(string relativePath)
|
||||||
|
=> File.ReadAllText(Path.Combine(TvRoot, relativePath.Replace('/', Path.DirectorySeparatorChar)));
|
||||||
|
|
||||||
|
private static string FindRepoRoot()
|
||||||
|
{
|
||||||
|
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
|
while (current is not null)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(Path.Combine(current.FullName, "apps"))
|
||||||
|
&& File.Exists(Path.Combine(current.FullName, "README.md")))
|
||||||
|
{
|
||||||
|
return current.FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DirectoryNotFoundException("Could not find bluejay-infra root.");
|
||||||
|
}
|
||||||
|
}
|
||||||
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