Compare commits
1 Commits
codex/s50-
...
runners/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59543016c0 |
@@ -1,33 +0,0 @@
|
|||||||
# FlowerCore SignalControl platform notes
|
|
||||||
|
|
||||||
This app owns the cluster web manager at `signalcontrol.iamworkin.lan` and documents the physical Pi pilot at `signal-a.iamworkin.lan` / `pirelay`.
|
|
||||||
|
|
||||||
## mTLS enrollment pattern
|
|
||||||
|
|
||||||
Do not install or restart anything from this repo. The intended pirelay pattern is the Pi-signage step-ca-agent shape:
|
|
||||||
|
|
||||||
- stable node identity: `pirelay`
|
|
||||||
- local private key and CSR generated on the node
|
|
||||||
- CSR submitted through the approved DeviceManagement/step-ca enrollment path
|
|
||||||
- client certificate and chain stored node-local under `/etc/flowercore/signalcontrol/mtls/`
|
|
||||||
- daily renewal timer, renewing only when fewer than 30 days remain
|
|
||||||
- certificate used for DM-agent to DM-web traffic and future SignalControl inter-service calls
|
|
||||||
|
|
||||||
Secrets, enrollment codes, private keys, p12 passphrases, and OIDC client secrets stay out of Git.
|
|
||||||
|
|
||||||
## Telemetry
|
|
||||||
|
|
||||||
Monitoring manifests add a dedicated Prometheus job:
|
|
||||||
|
|
||||||
- `signalcontrol-pi-app`
|
|
||||||
- target `10.0.58.113:5200`
|
|
||||||
- path `/metrics/prometheus`
|
|
||||||
- labels `instance="pirelay"`, `host="signal-a.iamworkin.lan"`, `service="signalcontrol-pi"`
|
|
||||||
|
|
||||||
Host metrics continue through the `edge-nodes` node_exporter target at `10.0.58.113:9100`.
|
|
||||||
|
|
||||||
## Physical-control audit
|
|
||||||
|
|
||||||
The app ships with `FlowerCore:SignalControl:PhysicalAudit:Enabled=false` and `ForwardingEnabled=false`. Enabling local audit creates a SHA-256 hash chain for physical-control mutations. Forwarding to `https://audit.iamworkin.lan/api/v1/audit/signalcontrol` requires flipping the forwarding gate separately.
|
|
||||||
|
|
||||||
Telemetry reads and `/metrics` scrapes are not audited.
|
|
||||||
@@ -46,7 +46,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: signalcontrol-web
|
- name: signalcontrol-web
|
||||||
image: localhost/fc-signalcontrol-web:s50cx12-20260602-1d26c58
|
image: localhost/fc-signalcontrol-web:latest
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 5000
|
- containerPort: 5000
|
||||||
@@ -65,48 +65,6 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: signalcontrol-auth
|
name: signalcontrol-auth
|
||||||
key: Auth__ApiKey
|
key: Auth__ApiKey
|
||||||
- name: Auth__AdminApiKey
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: signalcontrol-auth
|
|
||||||
key: Auth__AdminApiKey
|
|
||||||
optional: true
|
|
||||||
- name: Auth__Enabled
|
|
||||||
value: "false"
|
|
||||||
- name: FlowerCore__Auth__Enabled
|
|
||||||
value: "false"
|
|
||||||
- name: FlowerCore__Auth__Oidc__Enabled
|
|
||||||
value: "true"
|
|
||||||
- name: FlowerCore__Auth__Oidc__Authority
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: signalcontrol-oidc-client
|
|
||||||
key: issuer_url
|
|
||||||
optional: true
|
|
||||||
- name: FlowerCore__Auth__Oidc__ClientId
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: signalcontrol-oidc-client
|
|
||||||
key: client_id
|
|
||||||
optional: true
|
|
||||||
- name: FlowerCore__Auth__Oidc__ClientSecret
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: signalcontrol-oidc-client
|
|
||||||
key: client_secret
|
|
||||||
optional: true
|
|
||||||
- name: TrafficSignal__RelayBridge__Enabled
|
|
||||||
value: "true"
|
|
||||||
- name: TrafficSignal__RelayBridge__BaseUrl
|
|
||||||
value: https://pirelay.iamworkin.lan
|
|
||||||
- name: TrafficSignal__RelayBridge__ApiKey
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: signalcontrol-pirelay
|
|
||||||
key: ApiKey
|
|
||||||
optional: true
|
|
||||||
- name: LiveStatus__TrafficSignal__BaseAddress
|
|
||||||
value: https://signalcontrol.iamworkin.lan
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: data
|
- name: data
|
||||||
mountPath: /data
|
mountPath: /data
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ spec:
|
|||||||
fsGroupChangePolicy: OnRootMismatch
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: localhost/fc-ttsreader-web:v20260531-tts-corrections-r2
|
image: localhost/fc-ttsreader-web:v20260518-sprint36-demo-finish-b132cbf
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 5217
|
- containerPort: 5217
|
||||||
@@ -554,8 +554,6 @@ 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:v202605310029-7974fc4
|
image: localhost/fc-updater-web:v20260509-4162dca-authgate
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
@@ -88,8 +88,6 @@ 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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 3
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
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: "100m"
|
cpu: "500m"
|
||||||
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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-tts-reader
|
app.kubernetes.io/name: github-runner-tts-reader
|
||||||
@@ -1726,17 +1726,13 @@ spec:
|
|||||||
key: credential
|
key: credential
|
||||||
- name: RUN_AS_ROOT
|
- name: RUN_AS_ROOT
|
||||||
value: "false"
|
value: "false"
|
||||||
# Bumped 2026-05-25: previous 4Gi limit caused OOMKill (exit 137)
|
|
||||||
# mid-`dotnet test` on TtsReader's 1000+ test suite. PR #21 CI flapped
|
|
||||||
# twice with "runner lost communication" before the K8s side
|
|
||||||
# symptoms surfaced. 8Gi gives ~30% headroom over peak observed.
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "2Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
memory: "8Gi"
|
memory: "4Gi"
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: runner-home
|
- name: runner-home
|
||||||
mountPath: /home/runner
|
mountPath: /home/runner
|
||||||
@@ -1867,7 +1863,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2001,7 +1997,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2135,7 +2131,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2269,7 +2265,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2317,7 +2313,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-remote-desktop
|
app.kubernetes.io/name: github-runner-remote-desktop
|
||||||
@@ -2402,7 +2398,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2536,7 +2532,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2584,7 +2580,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-distribution
|
app.kubernetes.io/name: github-runner-distribution
|
||||||
@@ -2669,7 +2665,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2717,7 +2713,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-scoreboard
|
app.kubernetes.io/name: github-runner-scoreboard
|
||||||
@@ -2802,7 +2798,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2850,7 +2846,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-segment-display
|
app.kubernetes.io/name: github-runner-segment-display
|
||||||
@@ -2935,7 +2931,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -2983,7 +2979,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-signage-contracts
|
app.kubernetes.io/name: github-runner-signage-contracts
|
||||||
@@ -3068,7 +3064,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3116,7 +3112,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-signal-control
|
app.kubernetes.io/name: github-runner-signal-control
|
||||||
@@ -3201,7 +3197,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3335,7 +3331,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3469,7 +3465,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3603,7 +3599,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3737,7 +3733,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3871,7 +3867,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -3919,7 +3915,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-pimanager
|
app.kubernetes.io/name: github-runner-pimanager
|
||||||
@@ -4004,7 +4000,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -4053,7 +4049,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: 1
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: github-runner-updater
|
app.kubernetes.io/name: github-runner-updater
|
||||||
@@ -4138,419 +4134,7 @@ spec:
|
|||||||
value: "false"
|
value: "false"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: "100m"
|
cpu: "500m"
|
||||||
memory: "1Gi"
|
|
||||||
limits:
|
|
||||||
cpu: "2000m"
|
|
||||||
memory: "4Gi"
|
|
||||||
volumeMounts:
|
|
||||||
- name: runner-home
|
|
||||||
mountPath: /home/runner
|
|
||||||
- name: nuget-cache
|
|
||||||
mountPath: /home/runner/.nuget/packages
|
|
||||||
- name: tmp
|
|
||||||
mountPath: /tmp
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- "pgrep -f Runner.Listener > /dev/null"
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 30
|
|
||||||
failureThreshold: 3
|
|
||||||
volumes:
|
|
||||||
- name: runner-home
|
|
||||||
emptyDir: {}
|
|
||||||
- name: nuget-cache
|
|
||||||
emptyDir:
|
|
||||||
sizeLimit: 2Gi
|
|
||||||
- name: tmp
|
|
||||||
emptyDir: {}
|
|
||||||
restartPolicy: Always
|
|
||||||
---
|
|
||||||
# Runner for FlowerCore.DeviceManagement. Two replicas use per-pod emptyDir
|
|
||||||
# caches, so backlog can drain without sharing a ReadWriteOnce PVC. Added
|
|
||||||
# 2026-05-26 morning-routine — DM had ZERO registered runners while Sprint 37
|
|
||||||
# Cx-1 PRs #20 (CI-to-Linux migration), #21 (WDAC), and #22 (AppLocker) were
|
|
||||||
# all queued indefinitely. Chicken-and-egg: the migration PRs need Linux
|
|
||||||
# runners that the migration creates.
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: github-runner-device-management
|
|
||||||
namespace: github-runner
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: github-runner-device-management
|
|
||||||
app.kubernetes.io/component: runner
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/managed-by: argocd
|
|
||||||
flowercore.io/created-by: argocd
|
|
||||||
flowercore.io/runner-repo: device-management
|
|
||||||
flowercore.io/github-repo: FlowerCore.DeviceManagement
|
|
||||||
spec:
|
|
||||||
# Single replica until cluster CPU pressure resolves; the fleet-wide
|
|
||||||
# request right-sizing pass is queued for a future sweep.
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: github-runner-device-management
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: github-runner-device-management
|
|
||||||
app.kubernetes.io/component: runner
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
flowercore.io/created-by: argocd
|
|
||||||
flowercore.io/runner-repo: device-management
|
|
||||||
flowercore.io/github-repo: FlowerCore.DeviceManagement
|
|
||||||
spec:
|
|
||||||
serviceAccountName: github-runner
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1001
|
|
||||||
runAsGroup: 1001
|
|
||||||
fsGroup: 1001
|
|
||||||
initContainers:
|
|
||||||
- name: setup-runner-home
|
|
||||||
image: localhost/fc-github-runner:v20260525-ruby3.3.11-stepca
|
|
||||||
imagePullPolicy: Never
|
|
||||||
command:
|
|
||||||
- sh
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
set -e
|
|
||||||
mkdir -p /home/runner/.dotnet /home/runner/.nuget/packages /home/runner/.nuget/NuGet /home/runner/.cache /home/runner/_tool
|
|
||||||
if [ -d /opt/runner-toolcache/Ruby ] && [ ! -d /home/runner/_tool/Ruby ]; then
|
|
||||||
cp -a /opt/runner-toolcache/Ruby /home/runner/_tool/
|
|
||||||
fi
|
|
||||||
chown -R 1001:1001 /home/runner/.dotnet /home/runner/.nuget /home/runner/.cache /home/runner/_tool
|
|
||||||
chmod -R 755 /home/runner/.dotnet /home/runner/.nuget /home/runner/.cache /home/runner/_tool
|
|
||||||
securityContext:
|
|
||||||
runAsUser: 0
|
|
||||||
runAsNonRoot: false
|
|
||||||
volumeMounts:
|
|
||||||
- name: runner-home
|
|
||||||
mountPath: /home/runner
|
|
||||||
containers:
|
|
||||||
- name: runner
|
|
||||||
image: localhost/fc-github-runner:v20260525-ruby3.3.11-stepca
|
|
||||||
imagePullPolicy: Never
|
|
||||||
env:
|
|
||||||
- name: REPO_URL
|
|
||||||
value: "https://github.com/astoltz/FlowerCore.DeviceManagement"
|
|
||||||
- name: RUNNER_NAME_PREFIX
|
|
||||||
value: "rke2-linux-device-management"
|
|
||||||
- name: RUNNER_WORKDIR
|
|
||||||
value: "/tmp/runner/work"
|
|
||||||
- name: EPHEMERAL
|
|
||||||
value: "true"
|
|
||||||
- name: LABELS
|
|
||||||
value: "self-hosted,linux,fc-build-linux"
|
|
||||||
- name: HOME
|
|
||||||
value: "/home/runner"
|
|
||||||
- name: DOTNET_INSTALL_DIR
|
|
||||||
value: "/home/runner/.dotnet"
|
|
||||||
- name: DOTNET_CLI_TELEMETRY_OPTOUT
|
|
||||||
value: "1"
|
|
||||||
- name: DOTNET_NOLOGO
|
|
||||||
value: "1"
|
|
||||||
- name: DOTNET_GENERATE_ASPNET_CERTIFICATE
|
|
||||||
value: "false"
|
|
||||||
- name: DOTNET_CLI_HOME
|
|
||||||
value: "/home/runner"
|
|
||||||
- name: NUGET_PACKAGES
|
|
||||||
value: "/home/runner/.nuget/packages"
|
|
||||||
- name: XDG_CACHE_HOME
|
|
||||||
value: "/home/runner/.cache"
|
|
||||||
- name: RUNNER_TOOL_CACHE
|
|
||||||
value: "/home/runner/_tool"
|
|
||||||
- name: ACCESS_TOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: github-runner-token
|
|
||||||
key: credential
|
|
||||||
- name: RUN_AS_ROOT
|
|
||||||
value: "false"
|
|
||||||
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:
|
|
||||||
cpu: "100m"
|
|
||||||
memory: "1Gi"
|
|
||||||
limits:
|
|
||||||
cpu: "2000m"
|
|
||||||
memory: "4Gi"
|
|
||||||
volumeMounts:
|
|
||||||
- name: runner-home
|
|
||||||
mountPath: /home/runner
|
|
||||||
- name: nuget-cache
|
|
||||||
mountPath: /home/runner/.nuget/packages
|
|
||||||
- name: tmp
|
|
||||||
mountPath: /tmp
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- "pgrep -f Runner.Listener > /dev/null"
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 30
|
|
||||||
failureThreshold: 3
|
|
||||||
volumes:
|
|
||||||
- name: runner-home
|
|
||||||
emptyDir: {}
|
|
||||||
- name: nuget-cache
|
|
||||||
emptyDir:
|
|
||||||
sizeLimit: 2Gi
|
|
||||||
- name: tmp
|
|
||||||
emptyDir: {}
|
|
||||||
restartPolicy: Always
|
|
||||||
---
|
|
||||||
# Runner for FlowerCore.AiStation.Linux. Two replicas use per-pod emptyDir
|
|
||||||
# caches. Added 2026-05-26 — #13 master-CI-fix PR was queued indefinitely
|
|
||||||
# (Linux job has no runner; Windows job remains queued until the Windows
|
|
||||||
# runner host substrate lands per Sprint 36 v2 Cl-2 / ADR-174).
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: github-runner-aistation-linux
|
|
||||||
namespace: github-runner
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: github-runner-aistation-linux
|
|
||||||
app.kubernetes.io/component: runner
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/managed-by: argocd
|
|
||||||
flowercore.io/created-by: argocd
|
|
||||||
flowercore.io/runner-repo: aistation-linux
|
|
||||||
flowercore.io/github-repo: FlowerCore.AiStation.Linux
|
|
||||||
spec:
|
|
||||||
# Single replica until cluster CPU pressure resolves; the fleet-wide
|
|
||||||
# request right-sizing pass is queued for a future sweep.
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: github-runner-aistation-linux
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: github-runner-aistation-linux
|
|
||||||
app.kubernetes.io/component: runner
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
flowercore.io/created-by: argocd
|
|
||||||
flowercore.io/runner-repo: aistation-linux
|
|
||||||
flowercore.io/github-repo: FlowerCore.AiStation.Linux
|
|
||||||
spec:
|
|
||||||
serviceAccountName: github-runner
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1001
|
|
||||||
runAsGroup: 1001
|
|
||||||
fsGroup: 1001
|
|
||||||
initContainers:
|
|
||||||
- name: setup-runner-home
|
|
||||||
image: localhost/fc-github-runner:v20260525-ruby3.3.11-stepca
|
|
||||||
imagePullPolicy: Never
|
|
||||||
command:
|
|
||||||
- sh
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
set -e
|
|
||||||
mkdir -p /home/runner/.dotnet /home/runner/.nuget/packages /home/runner/.nuget/NuGet /home/runner/.cache /home/runner/_tool
|
|
||||||
if [ -d /opt/runner-toolcache/Ruby ] && [ ! -d /home/runner/_tool/Ruby ]; then
|
|
||||||
cp -a /opt/runner-toolcache/Ruby /home/runner/_tool/
|
|
||||||
fi
|
|
||||||
chown -R 1001:1001 /home/runner/.dotnet /home/runner/.nuget /home/runner/.cache /home/runner/_tool
|
|
||||||
chmod -R 755 /home/runner/.dotnet /home/runner/.nuget /home/runner/.cache /home/runner/_tool
|
|
||||||
securityContext:
|
|
||||||
runAsUser: 0
|
|
||||||
runAsNonRoot: false
|
|
||||||
volumeMounts:
|
|
||||||
- name: runner-home
|
|
||||||
mountPath: /home/runner
|
|
||||||
containers:
|
|
||||||
- name: runner
|
|
||||||
image: localhost/fc-github-runner:v20260525-ruby3.3.11-stepca
|
|
||||||
imagePullPolicy: Never
|
|
||||||
env:
|
|
||||||
- name: REPO_URL
|
|
||||||
value: "https://github.com/astoltz/FlowerCore.AiStation.Linux"
|
|
||||||
- name: RUNNER_NAME_PREFIX
|
|
||||||
value: "rke2-linux-aistation-linux"
|
|
||||||
- name: RUNNER_WORKDIR
|
|
||||||
value: "/tmp/runner/work"
|
|
||||||
- name: EPHEMERAL
|
|
||||||
value: "true"
|
|
||||||
- name: LABELS
|
|
||||||
value: "self-hosted,linux,fc-build-linux"
|
|
||||||
- name: HOME
|
|
||||||
value: "/home/runner"
|
|
||||||
- name: DOTNET_INSTALL_DIR
|
|
||||||
value: "/home/runner/.dotnet"
|
|
||||||
- name: DOTNET_CLI_TELEMETRY_OPTOUT
|
|
||||||
value: "1"
|
|
||||||
- name: DOTNET_NOLOGO
|
|
||||||
value: "1"
|
|
||||||
- name: DOTNET_GENERATE_ASPNET_CERTIFICATE
|
|
||||||
value: "false"
|
|
||||||
- name: DOTNET_CLI_HOME
|
|
||||||
value: "/home/runner"
|
|
||||||
- name: NUGET_PACKAGES
|
|
||||||
value: "/home/runner/.nuget/packages"
|
|
||||||
- name: XDG_CACHE_HOME
|
|
||||||
value: "/home/runner/.cache"
|
|
||||||
- name: RUNNER_TOOL_CACHE
|
|
||||||
value: "/home/runner/_tool"
|
|
||||||
- name: ACCESS_TOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: github-runner-token
|
|
||||||
key: credential
|
|
||||||
- name: RUN_AS_ROOT
|
|
||||||
value: "false"
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "100m"
|
|
||||||
memory: "1Gi"
|
|
||||||
limits:
|
|
||||||
cpu: "2000m"
|
|
||||||
memory: "4Gi"
|
|
||||||
volumeMounts:
|
|
||||||
- name: runner-home
|
|
||||||
mountPath: /home/runner
|
|
||||||
- name: nuget-cache
|
|
||||||
mountPath: /home/runner/.nuget/packages
|
|
||||||
- name: tmp
|
|
||||||
mountPath: /tmp
|
|
||||||
livenessProbe:
|
|
||||||
exec:
|
|
||||||
command:
|
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- "pgrep -f Runner.Listener > /dev/null"
|
|
||||||
initialDelaySeconds: 30
|
|
||||||
periodSeconds: 30
|
|
||||||
failureThreshold: 3
|
|
||||||
volumes:
|
|
||||||
- name: runner-home
|
|
||||||
emptyDir: {}
|
|
||||||
- name: nuget-cache
|
|
||||||
emptyDir:
|
|
||||||
sizeLimit: 2Gi
|
|
||||||
- name: tmp
|
|
||||||
emptyDir: {}
|
|
||||||
restartPolicy: Always
|
|
||||||
---
|
|
||||||
# Runner for FlowerCore.WorldBuilder. Two replicas use per-pod emptyDir
|
|
||||||
# caches. Added 2026-05-26 — #3 and #4 Linux migration PRs queued
|
|
||||||
# indefinitely with one stale offline runner registered against the repo.
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: github-runner-worldbuilder
|
|
||||||
namespace: github-runner
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: github-runner-worldbuilder
|
|
||||||
app.kubernetes.io/component: runner
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/managed-by: argocd
|
|
||||||
flowercore.io/created-by: argocd
|
|
||||||
flowercore.io/runner-repo: worldbuilder
|
|
||||||
flowercore.io/github-repo: FlowerCore.WorldBuilder
|
|
||||||
spec:
|
|
||||||
# Single replica until cluster CPU pressure resolves; the fleet-wide
|
|
||||||
# request right-sizing pass is queued for a future sweep.
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: github-runner-worldbuilder
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: github-runner-worldbuilder
|
|
||||||
app.kubernetes.io/component: runner
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
flowercore.io/created-by: argocd
|
|
||||||
flowercore.io/runner-repo: worldbuilder
|
|
||||||
flowercore.io/github-repo: FlowerCore.WorldBuilder
|
|
||||||
spec:
|
|
||||||
serviceAccountName: github-runner
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1001
|
|
||||||
runAsGroup: 1001
|
|
||||||
fsGroup: 1001
|
|
||||||
initContainers:
|
|
||||||
- name: setup-runner-home
|
|
||||||
image: localhost/fc-github-runner:v20260525-ruby3.3.11-stepca
|
|
||||||
imagePullPolicy: Never
|
|
||||||
command:
|
|
||||||
- sh
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
set -e
|
|
||||||
mkdir -p /home/runner/.dotnet /home/runner/.nuget/packages /home/runner/.nuget/NuGet /home/runner/.cache /home/runner/_tool
|
|
||||||
if [ -d /opt/runner-toolcache/Ruby ] && [ ! -d /home/runner/_tool/Ruby ]; then
|
|
||||||
cp -a /opt/runner-toolcache/Ruby /home/runner/_tool/
|
|
||||||
fi
|
|
||||||
chown -R 1001:1001 /home/runner/.dotnet /home/runner/.nuget /home/runner/.cache /home/runner/_tool
|
|
||||||
chmod -R 755 /home/runner/.dotnet /home/runner/.nuget /home/runner/.cache /home/runner/_tool
|
|
||||||
securityContext:
|
|
||||||
runAsUser: 0
|
|
||||||
runAsNonRoot: false
|
|
||||||
volumeMounts:
|
|
||||||
- name: runner-home
|
|
||||||
mountPath: /home/runner
|
|
||||||
containers:
|
|
||||||
- name: runner
|
|
||||||
image: localhost/fc-github-runner:v20260525-ruby3.3.11-stepca
|
|
||||||
imagePullPolicy: Never
|
|
||||||
env:
|
|
||||||
- name: REPO_URL
|
|
||||||
value: "https://github.com/astoltz/FlowerCore.WorldBuilder"
|
|
||||||
- name: RUNNER_NAME_PREFIX
|
|
||||||
value: "rke2-linux-worldbuilder"
|
|
||||||
- name: RUNNER_WORKDIR
|
|
||||||
value: "/tmp/runner/work"
|
|
||||||
- name: EPHEMERAL
|
|
||||||
value: "true"
|
|
||||||
- name: LABELS
|
|
||||||
value: "self-hosted,linux,fc-build-linux"
|
|
||||||
- name: HOME
|
|
||||||
value: "/home/runner"
|
|
||||||
- name: DOTNET_INSTALL_DIR
|
|
||||||
value: "/home/runner/.dotnet"
|
|
||||||
- name: DOTNET_CLI_TELEMETRY_OPTOUT
|
|
||||||
value: "1"
|
|
||||||
- name: DOTNET_NOLOGO
|
|
||||||
value: "1"
|
|
||||||
- name: DOTNET_GENERATE_ASPNET_CERTIFICATE
|
|
||||||
value: "false"
|
|
||||||
- name: DOTNET_CLI_HOME
|
|
||||||
value: "/home/runner"
|
|
||||||
- name: NUGET_PACKAGES
|
|
||||||
value: "/home/runner/.nuget/packages"
|
|
||||||
- name: XDG_CACHE_HOME
|
|
||||||
value: "/home/runner/.cache"
|
|
||||||
- name: RUNNER_TOOL_CACHE
|
|
||||||
value: "/home/runner/_tool"
|
|
||||||
- name: ACCESS_TOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: github-runner-token
|
|
||||||
key: credential
|
|
||||||
- name: RUN_AS_ROOT
|
|
||||||
value: "false"
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: "100m"
|
|
||||||
memory: "1Gi"
|
memory: "1Gi"
|
||||||
limits:
|
limits:
|
||||||
cpu: "2000m"
|
cpu: "2000m"
|
||||||
@@ -4587,6 +4171,3 @@ spec:
|
|||||||
# Common as the only PVC-backed runner at replicas: 1. Any future multi-replica
|
# Common as the only PVC-backed runner at replicas: 1. Any future multi-replica
|
||||||
# runner must use per-pod emptyDir caches, not a shared ReadWriteOnce PVC.
|
# runner must use per-pod emptyDir caches, not a shared ReadWriteOnce PVC.
|
||||||
# 2026-05-25: PiManager added (was missed in the Sprint 32 long-tail sweep).
|
# 2026-05-25: PiManager added (was missed in the Sprint 32 long-tail sweep).
|
||||||
# 2026-05-26: Updater + DeviceManagement + AiStation.Linux + WorldBuilder
|
|
||||||
# added by the morning-routine sweep — those repos had had ZERO online Linux
|
|
||||||
# PR-CI capacity, blocking the Sprint 37 Cx-1 Linux-CI-migration PRs.
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: intranet-web
|
- name: intranet-web
|
||||||
image: localhost/fc-intranet-web:v20260531-ttsreader-bridge
|
image: localhost/fc-intranet-web:v20260508-brochure-w1
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 5300
|
- containerPort: 5300
|
||||||
|
|||||||
@@ -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: Halted
|
runStrategy: Always
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -207,13 +207,20 @@ spec:
|
|||||||
- port: 993
|
- port: 993
|
||||||
targetPort: 993
|
targetPort: 993
|
||||||
name: imaps
|
name: imaps
|
||||||
# --- mail-tls Certificate REMOVED 2026-06-01 ---
|
---
|
||||||
# mail-tls is now managed OUTSIDE cert-manager: issued from step-ca's JWK 'admin'
|
# TLS Certificate via cert-manager
|
||||||
# provisioner and auto-renewed by a systemd timer on noc1 (step ca renew), which
|
apiVersion: cert-manager.io/v1
|
||||||
# writes the mail-tls secret directly. step-ca-acme only has an HTTP-01 (Traefik)
|
kind: Certificate
|
||||||
# solver, but mail.iamworkin.lan must resolve to the dedicated MetalLB IP 10.0.56.202
|
metadata:
|
||||||
# (SMTP/IMAP), so HTTP-01 cannot validate. Do NOT re-add a cert-manager Certificate
|
name: mail-tls
|
||||||
# here unless a DNS-01 solver is deployed for step-ca-acme.
|
namespace: mail
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
# Grafana dashboard ConfigMap for FlowerCore.SignalControl on pirelay.
|
|
||||||
#
|
|
||||||
# The Grafana Deployment in noc-monitoring.yaml mounts this ConfigMap at
|
|
||||||
# /var/lib/grafana/dashboards/signalcontrol. The paired Prometheus jobs are:
|
|
||||||
# - signalcontrol-pi-app: 10.0.58.113:5200 /metrics/prometheus
|
|
||||||
# - edge-nodes: 10.0.58.113:9100 with instance="pirelay"
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: grafana-dashboard-signalcontrol
|
|
||||||
namespace: monitoring
|
|
||||||
data:
|
|
||||||
signalcontrol.json: |
|
|
||||||
{
|
|
||||||
"annotations": { "list": [] },
|
|
||||||
"editable": true,
|
|
||||||
"fiscalYearStartMonth": 0,
|
|
||||||
"graphTooltip": 0,
|
|
||||||
"id": null,
|
|
||||||
"links": [],
|
|
||||||
"panels": [
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{ "color": "red", "value": null },
|
|
||||||
{ "color": "green", "value": 1 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "short"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": { "h": 5, "w": 6, "x": 0, "y": 0 },
|
|
||||||
"id": 1,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "none",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false },
|
|
||||||
"textMode": "auto"
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{ "editorMode": "code", "expr": "up{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}", "range": true, "refId": "A" }
|
|
||||||
],
|
|
||||||
"title": "SignalControl App Up",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{ "color": "red", "value": null },
|
|
||||||
{ "color": "green", "value": 1 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "short"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": { "h": 5, "w": 6, "x": 6, "y": 0 },
|
|
||||||
"id": 2,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "none",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false },
|
|
||||||
"textMode": "auto"
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{ "editorMode": "code", "expr": "up{job=\"edge-nodes\",instance=\"pirelay\"}", "range": true, "refId": "A" }
|
|
||||||
],
|
|
||||||
"title": "pirelay node_exporter Up",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 5, "w": 6, "x": 12, "y": 0 },
|
|
||||||
"id": 3,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false },
|
|
||||||
"textMode": "name"
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{ "editorMode": "code", "expr": "signalcontrol_active_pattern{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}", "legendFormat": "{{pattern}}", "range": true, "refId": "A" }
|
|
||||||
],
|
|
||||||
"title": "Active Pattern",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 5, "w": 6, "x": 18, "y": 0 },
|
|
||||||
"id": 4,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false },
|
|
||||||
"textMode": "name"
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{ "editorMode": "code", "expr": "signalcontrol_phase{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}", "legendFormat": "{{phase}}", "range": true, "refId": "A" }
|
|
||||||
],
|
|
||||||
"title": "Current Phase",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
|
|
||||||
"id": 5,
|
|
||||||
"options": { "legend": { "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "single" } },
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (channel, state) (rate(signal_relay_writes_total{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}[$__rate_interval]))",
|
|
||||||
"legendFormat": "channel {{channel}} {{state}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Relay Activations",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
|
|
||||||
"id": 6,
|
|
||||||
"options": { "legend": { "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "single" } },
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (source, to) (rate(signal_transitions_total{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}[$__rate_interval]))",
|
|
||||||
"legendFormat": "{{source}} -> {{to}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Phase Dwell / Transitions",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 },
|
|
||||||
"id": 7,
|
|
||||||
"options": { "legend": { "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "single" } },
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (action, outcome) (increase(signal_schedule_fires_total{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}[24h]))",
|
|
||||||
"legendFormat": "{{action}} {{outcome}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "sum by (from_pattern, to_pattern) (increase(flowercore_signalcontrol_pattern_switches_total{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}[24h]))",
|
|
||||||
"legendFormat": "{{from_pattern}} -> {{to_pattern}}",
|
|
||||||
"range": true,
|
|
||||||
"refId": "B"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Schedule Fires and Pattern Switches",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "percentunit" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 },
|
|
||||||
"id": 8,
|
|
||||||
"options": { "legend": { "displayMode": "table", "placement": "bottom" }, "tooltip": { "mode": "single" } },
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "1 - avg by (instance) (rate(node_cpu_seconds_total{job=\"edge-nodes\",instance=\"pirelay\",mode=\"idle\"}[$__rate_interval]))",
|
|
||||||
"legendFormat": "CPU",
|
|
||||||
"range": true,
|
|
||||||
"refId": "A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"editorMode": "code",
|
|
||||||
"expr": "1 - (node_memory_MemAvailable_bytes{job=\"edge-nodes\",instance=\"pirelay\"} / node_memory_MemTotal_bytes{job=\"edge-nodes\",instance=\"pirelay\"})",
|
|
||||||
"legendFormat": "Memory",
|
|
||||||
"range": true,
|
|
||||||
"refId": "B"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "pirelay Host Utilization",
|
|
||||||
"type": "timeseries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 21 },
|
|
||||||
"id": 9,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false },
|
|
||||||
"textMode": "auto"
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{ "editorMode": "code", "expr": "signalcontrol_screen_saver_enabled{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}", "range": true, "refId": "A" }
|
|
||||||
],
|
|
||||||
"title": "Screen-saver Enabled",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
||||||
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
|
||||||
"gridPos": { "h": 6, "w": 12, "x": 12, "y": 21 },
|
|
||||||
"id": 10,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false },
|
|
||||||
"textMode": "name"
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{ "editorMode": "code", "expr": "signalcontrol_animation_active{job=\"signalcontrol-pi-app\",instance=\"pirelay\"}", "legendFormat": "{{planner}}", "range": true, "refId": "A" }
|
|
||||||
],
|
|
||||||
"title": "Screen-saver / Animation Engaged",
|
|
||||||
"type": "stat"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"refresh": "30s",
|
|
||||||
"schemaVersion": 39,
|
|
||||||
"style": "dark",
|
|
||||||
"tags": [ "flowercore", "signalcontrol", "pirelay" ],
|
|
||||||
"templating": { "list": [] },
|
|
||||||
"time": { "from": "now-24h", "to": "now" },
|
|
||||||
"timezone": "browser",
|
|
||||||
"title": "FlowerCore SignalControl",
|
|
||||||
"uid": "flowercore-signalcontrol",
|
|
||||||
"version": 1
|
|
||||||
}
|
|
||||||
@@ -230,19 +230,6 @@ data:
|
|||||||
vlan: "home"
|
vlan: "home"
|
||||||
device: "pi3-ks0212"
|
device: "pi3-ks0212"
|
||||||
|
|
||||||
# SignalControl Pi-edition app metrics (pirelay / signal-a)
|
|
||||||
- job_name: "signalcontrol-pi-app"
|
|
||||||
scrape_interval: 15s
|
|
||||||
metrics_path: /metrics/prometheus
|
|
||||||
static_configs:
|
|
||||||
- targets: ["10.0.58.113:5200"]
|
|
||||||
labels:
|
|
||||||
instance: "pirelay"
|
|
||||||
host: "signal-a.iamworkin.lan"
|
|
||||||
service: "signalcontrol-pi"
|
|
||||||
vlan: "home"
|
|
||||||
device: "pi3-ks0212"
|
|
||||||
|
|
||||||
# Epson ET-3750 EcoTank Printer SNMP
|
# Epson ET-3750 EcoTank Printer SNMP
|
||||||
- job_name: "snmp-printer"
|
- job_name: "snmp-printer"
|
||||||
scrape_interval: 5m
|
scrape_interval: 5m
|
||||||
@@ -492,11 +479,11 @@ 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/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
- "https://signage.iamworkin.lan/"
|
||||||
- "https://kiosk.iamworkin.lan/"
|
- "https://kiosk.iamworkin.lan/"
|
||||||
- "https://media.iamworkin.lan/"
|
- "https://media.iamworkin.lan/"
|
||||||
- "https://mysql.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
- "https://mysql.iamworkin.lan/"
|
||||||
- "https://php.iamworkin.lan/healthz" # root 401 auth-gated 2026-06-01; /healthz anon 200
|
- "https://php.iamworkin.lan/"
|
||||||
- "https://zabbix.iamworkin.lan/"
|
- "https://zabbix.iamworkin.lan/"
|
||||||
- "https://desktop.iamworkin.lan/"
|
- "https://desktop.iamworkin.lan/"
|
||||||
- "https://print.iamworkin.lan/"
|
- "https://print.iamworkin.lan/"
|
||||||
@@ -4064,9 +4051,6 @@ spec:
|
|||||||
- name: dashboards-remotedesktop
|
- name: dashboards-remotedesktop
|
||||||
mountPath: /var/lib/grafana/dashboards/remotedesktop
|
mountPath: /var/lib/grafana/dashboards/remotedesktop
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- name: dashboards-signalcontrol
|
|
||||||
mountPath: /var/lib/grafana/dashboards/signalcontrol
|
|
||||||
readOnly: true
|
|
||||||
- name: datasource-provisioning
|
- name: datasource-provisioning
|
||||||
mountPath: /etc/grafana/provisioning/datasources
|
mountPath: /etc/grafana/provisioning/datasources
|
||||||
readOnly: true
|
readOnly: true
|
||||||
@@ -4120,9 +4104,6 @@ spec:
|
|||||||
- name: dashboards-remotedesktop
|
- name: dashboards-remotedesktop
|
||||||
configMap:
|
configMap:
|
||||||
name: grafana-dashboard-remotedesktop
|
name: grafana-dashboard-remotedesktop
|
||||||
- name: dashboards-signalcontrol
|
|
||||||
configMap:
|
|
||||||
name: grafana-dashboard-signalcontrol
|
|
||||||
- name: datasource-provisioning
|
- name: datasource-provisioning
|
||||||
configMap:
|
configMap:
|
||||||
name: grafana-datasource-provisioning
|
name: grafana-datasource-provisioning
|
||||||
|
|||||||
@@ -227,50 +227,6 @@ public sealed class FleetManifestLintTests
|
|||||||
violations.Should().BeEmpty();
|
violations.Should().BeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SignalControlDeployment_MustKeepAuthOffAndStageOidcSecret()
|
|
||||||
{
|
|
||||||
var deployment = Inventory.Documents.Single(document =>
|
|
||||||
document.Kind == "Deployment"
|
|
||||||
&& document.Namespace == "fc-signalcontrol"
|
|
||||||
&& document.Name == "signalcontrol-web"
|
|
||||||
&& document.RelativePath == "fc-signalcontrol/fc-signalcontrol.yaml");
|
|
||||||
var container = deployment.MainContainerMappings().Single(container =>
|
|
||||||
ManifestNodeExtensions.Scalar(container, "name") == "signalcontrol-web");
|
|
||||||
|
|
||||||
EnvValue(container, "Auth__Enabled").Should().Be("false");
|
|
||||||
EnvValue(container, "FlowerCore__Auth__Enabled").Should().Be("false");
|
|
||||||
EnvValue(container, "FlowerCore__Auth__Oidc__Enabled").Should().Be("true");
|
|
||||||
EnvSecretName(container, "FlowerCore__Auth__Oidc__Authority").Should().Be("signalcontrol-oidc-client");
|
|
||||||
EnvSecretKey(container, "FlowerCore__Auth__Oidc__Authority").Should().Be("issuer_url");
|
|
||||||
EnvSecretName(container, "FlowerCore__Auth__Oidc__ClientId").Should().Be("signalcontrol-oidc-client");
|
|
||||||
EnvSecretKey(container, "FlowerCore__Auth__Oidc__ClientId").Should().Be("client_id");
|
|
||||||
EnvSecretName(container, "FlowerCore__Auth__Oidc__ClientSecret").Should().Be("signalcontrol-oidc-client");
|
|
||||||
EnvSecretKey(container, "FlowerCore__Auth__Oidc__ClientSecret").Should().Be("client_secret");
|
|
||||||
EnvSecretOptional(container, "FlowerCore__Auth__Oidc__Authority").Should().BeTrue();
|
|
||||||
EnvSecretOptional(container, "FlowerCore__Auth__Oidc__ClientId").Should().BeTrue();
|
|
||||||
EnvSecretOptional(container, "FlowerCore__Auth__Oidc__ClientSecret").Should().BeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SignalControlDeployment_MustWirePirelayRelayBridgeSecret()
|
|
||||||
{
|
|
||||||
var deployment = Inventory.Documents.Single(document =>
|
|
||||||
document.Kind == "Deployment"
|
|
||||||
&& document.Namespace == "fc-signalcontrol"
|
|
||||||
&& document.Name == "signalcontrol-web"
|
|
||||||
&& document.RelativePath == "fc-signalcontrol/fc-signalcontrol.yaml");
|
|
||||||
var container = deployment.MainContainerMappings().Single(container =>
|
|
||||||
ManifestNodeExtensions.Scalar(container, "name") == "signalcontrol-web");
|
|
||||||
|
|
||||||
EnvValue(container, "TrafficSignal__RelayBridge__Enabled").Should().Be("true");
|
|
||||||
EnvValue(container, "TrafficSignal__RelayBridge__BaseUrl").Should().Be("https://pirelay.iamworkin.lan");
|
|
||||||
EnvSecretName(container, "TrafficSignal__RelayBridge__ApiKey").Should().Be("signalcontrol-pirelay");
|
|
||||||
EnvSecretKey(container, "TrafficSignal__RelayBridge__ApiKey").Should().Be("ApiKey");
|
|
||||||
EnvSecretOptional(container, "TrafficSignal__RelayBridge__ApiKey").Should().BeTrue();
|
|
||||||
EnvValue(container, "LiveStatus__TrafficSignal__BaseAddress").Should().Be("https://signalcontrol.iamworkin.lan");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GitHubRunnerFleet_MustRegisterRequiredReposAsRepoScopedDeployments()
|
public void GitHubRunnerFleet_MustRegisterRequiredReposAsRepoScopedDeployments()
|
||||||
{
|
{
|
||||||
@@ -280,7 +236,7 @@ public sealed class FleetManifestLintTests
|
|||||||
{
|
{
|
||||||
deployments.Should().ContainKey(expectedRunner.Key);
|
deployments.Should().ContainKey(expectedRunner.Key);
|
||||||
|
|
||||||
var container = deployments[expectedRunner.Key].MainContainerMappings().Should().ContainSingle().Subject;
|
var container = deployments[expectedRunner.Key].ContainerMappings().Should().ContainSingle().Subject;
|
||||||
EnvValue(container, "REPO_URL").Should().Be(expectedRunner.Value);
|
EnvValue(container, "REPO_URL").Should().Be(expectedRunner.Value);
|
||||||
EnvValue(container, "EPHEMERAL").Should().Be("true");
|
EnvValue(container, "EPHEMERAL").Should().Be("true");
|
||||||
EnvValue(container, "LABELS").Should().Be("self-hosted,linux,fc-build-linux");
|
EnvValue(container, "LABELS").Should().Be("self-hosted,linux,fc-build-linux");
|
||||||
@@ -296,7 +252,7 @@ public sealed class FleetManifestLintTests
|
|||||||
{
|
{
|
||||||
foreach (var deployment in GitHubRunnerDeployments().Values)
|
foreach (var deployment in GitHubRunnerDeployments().Values)
|
||||||
{
|
{
|
||||||
var container = deployment.MainContainerMappings().Should().ContainSingle().Subject;
|
var container = deployment.ContainerMappings().Should().ContainSingle().Subject;
|
||||||
|
|
||||||
foreach (var expectedEnv in WritableRunnerEnv)
|
foreach (var expectedEnv in WritableRunnerEnv)
|
||||||
{
|
{
|
||||||
@@ -323,10 +279,7 @@ public sealed class FleetManifestLintTests
|
|||||||
foreach (var deploymentName in ScaledLinuxRunnerDeployments)
|
foreach (var deploymentName in ScaledLinuxRunnerDeployments)
|
||||||
{
|
{
|
||||||
var deployment = deployments[deploymentName];
|
var deployment = deployments[deploymentName];
|
||||||
// Scaled runners must have >= 2 replicas (avoid single-pod bottleneck).
|
ReplicaCount(deployment).Should().Be(2);
|
||||||
// Individual deployments may be tuned upward per CI activity — see
|
|
||||||
// "runners: right-size replica counts per 14d CI activity (#24)".
|
|
||||||
ReplicaCount(deployment).Should().BeGreaterOrEqualTo(2, $"{deploymentName} is in the scaled set and must run with at least 2 replicas");
|
|
||||||
|
|
||||||
var volumes = deployment.MappingSequence("spec", "template", "spec", "volumes");
|
var volumes = deployment.MappingSequence("spec", "template", "spec", "volumes");
|
||||||
var claimNames = volumes
|
var claimNames = volumes
|
||||||
@@ -352,108 +305,6 @@ public sealed class FleetManifestLintTests
|
|||||||
.Be("github-runner-nuget-cache");
|
.Be("github-runner-nuget-cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Runners_MustNotPinToOperatorWorkstationHosts()
|
|
||||||
{
|
|
||||||
// CRITICAL SAFETY (operator directive 2026-05-26): BLUEJAY-WS is the
|
|
||||||
// operator's primary workstation — host of the 1Password Connect
|
|
||||||
// bearer token, fcadmin SSH keys to noc1, signing CA private keys,
|
|
||||||
// and source for every FC repo. A self-hosted GitHub Actions runner
|
|
||||||
// there would execute arbitrary PR code with that local access.
|
|
||||||
// Build-side analog of the Sprint 9 NEW safe-account exclusion gate
|
|
||||||
// (Puppet GPO/AppLocker/WDAC/audit-forwarder modules refuse to apply
|
|
||||||
// on BLUEJAY-WS). This lint asserts no GitHub-runner Deployment in
|
|
||||||
// apps/github-runner/ pins to a forbidden operator-workstation host
|
|
||||||
// via nodeName, nodeSelector, nodeAffinity, or tolerations.
|
|
||||||
// Existing legacy `bluejay-ws-sandbox-1` GitHub-registered runner is
|
|
||||||
// out of scope here (it's a runtime registration, not a K8s
|
|
||||||
// Deployment) — see CLAUDE.md "Common Mistakes" entry and
|
|
||||||
// feedback_bluejay_ws_never_public_runner.md.
|
|
||||||
var forbiddenHostPatterns = new[]
|
|
||||||
{
|
|
||||||
"bluejay-ws",
|
|
||||||
"BLUEJAY-WS",
|
|
||||||
"bluejay-ws.iamworkin.lan",
|
|
||||||
"iamworkin-ws",
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ContainsForbidden(string? value) =>
|
|
||||||
!string.IsNullOrWhiteSpace(value)
|
|
||||||
&& forbiddenHostPatterns.Any(pattern => value!.Contains(pattern, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
var violations = GitHubRunnerDeployments().Values.SelectMany(deployment =>
|
|
||||||
{
|
|
||||||
var local = new List<string>();
|
|
||||||
var podSpec = ManifestNodeExtensions.Mapping(deployment.Root, "spec", "template", "spec");
|
|
||||||
if (podSpec is null)
|
|
||||||
{
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeName: pins the pod to a specific node by name.
|
|
||||||
var nodeName = ManifestNodeExtensions.Scalar(podSpec, "nodeName");
|
|
||||||
if (ContainsForbidden(nodeName))
|
|
||||||
{
|
|
||||||
local.Add($"{deployment.Name} sets nodeName='{nodeName}' which targets a forbidden operator-workstation host.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeSelector: dict of label → value pinning the pod to nodes
|
|
||||||
// carrying matching labels. Examples that would trip this:
|
|
||||||
// kubernetes.io/hostname: bluejay-ws
|
|
||||||
// flowercore.io/host: bluejay-ws.iamworkin.lan
|
|
||||||
var nodeSelector = ManifestNodeExtensions.Mapping(podSpec, "nodeSelector");
|
|
||||||
if (nodeSelector is not null)
|
|
||||||
{
|
|
||||||
foreach (var entry in nodeSelector.Children)
|
|
||||||
{
|
|
||||||
var key = entry.Key is YamlScalarNode keyScalar ? keyScalar.Value : null;
|
|
||||||
var value = entry.Value is YamlScalarNode valueScalar ? valueScalar.Value : null;
|
|
||||||
if (ContainsForbidden(value))
|
|
||||||
{
|
|
||||||
local.Add($"{deployment.Name} has nodeSelector entry '{key}: {value}' which targets a forbidden operator-workstation host.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeAffinity: matchExpressions over node labels.
|
|
||||||
foreach (var term in ManifestNodeExtensions.MappingSequence(podSpec, "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms"))
|
|
||||||
{
|
|
||||||
foreach (var expr in ManifestNodeExtensions.MappingSequence(term, "matchExpressions"))
|
|
||||||
{
|
|
||||||
var key = ManifestNodeExtensions.Scalar(expr, "key");
|
|
||||||
foreach (var valueNode in ManifestNodeExtensions.ScalarSequence(expr, "values"))
|
|
||||||
{
|
|
||||||
if (ContainsForbidden(valueNode))
|
|
||||||
{
|
|
||||||
local.Add($"{deployment.Name} has nodeAffinity matchExpression '{key}' value '{valueNode}' which targets a forbidden operator-workstation host.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tolerations: scheduling onto a tainted operator-workstation
|
|
||||||
// node would let the runner run there. Forbid any toleration
|
|
||||||
// value that names the workstation.
|
|
||||||
foreach (var toleration in ManifestNodeExtensions.MappingSequence(podSpec, "tolerations"))
|
|
||||||
{
|
|
||||||
var key = ManifestNodeExtensions.Scalar(toleration, "key");
|
|
||||||
var value = ManifestNodeExtensions.Scalar(toleration, "value");
|
|
||||||
if (ContainsForbidden(key))
|
|
||||||
{
|
|
||||||
local.Add($"{deployment.Name} has toleration key '{key}' which targets a forbidden operator-workstation host.");
|
|
||||||
}
|
|
||||||
if (ContainsForbidden(value))
|
|
||||||
{
|
|
||||||
local.Add($"{deployment.Name} has toleration value '{value}' which targets a forbidden operator-workstation host.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return local;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
violations.Should().BeEmpty("BLUEJAY-WS / iamworkin-ws must never host a fleet GitHub Actions runner; see CLAUDE.md 'Registering BLUEJAY-WS as a fleet GitHub Actions runner' and feedback_bluejay_ws_never_public_runner.md");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Monitoring_MustAlertWhenLinuxRunnerDeploymentIsUnavailable()
|
public void Monitoring_MustAlertWhenLinuxRunnerDeploymentIsUnavailable()
|
||||||
{
|
{
|
||||||
@@ -468,36 +319,6 @@ public sealed class FleetManifestLintTests
|
|||||||
monitoring.Should().Contain("alert_channel: irc");
|
monitoring.Should().Contain("alert_channel: irc");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Monitoring_MustScrapeSignalControlPiAppAndMountDashboard()
|
|
||||||
{
|
|
||||||
var monitoring = File.ReadAllText(Path.Combine(Inventory.BluejayRoot, "apps", "monitoring", "noc-monitoring.yaml"));
|
|
||||||
|
|
||||||
monitoring.Should().Contain("job_name: \"signalcontrol-pi-app\"");
|
|
||||||
monitoring.Should().Contain("metrics_path: /metrics/prometheus");
|
|
||||||
monitoring.Should().Contain("10.0.58.113:5200");
|
|
||||||
monitoring.Should().Contain("host: \"signal-a.iamworkin.lan\"");
|
|
||||||
monitoring.Should().Contain("mountPath: /var/lib/grafana/dashboards/signalcontrol");
|
|
||||||
monitoring.Should().Contain("name: grafana-dashboard-signalcontrol");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SignalControlGrafanaDashboard_MustCoverAppNodeAndPhysicalControlMetrics()
|
|
||||||
{
|
|
||||||
var dashboard = File.ReadAllText(Path.Combine(
|
|
||||||
Inventory.BluejayRoot,
|
|
||||||
"apps",
|
|
||||||
"monitoring",
|
|
||||||
"grafana-dashboard-signalcontrol.yaml"));
|
|
||||||
|
|
||||||
dashboard.Should().Contain("uid\": \"flowercore-signalcontrol\"");
|
|
||||||
dashboard.Should().Contain("up{job=\\\"signalcontrol-pi-app\\\",instance=\\\"pirelay\\\"}");
|
|
||||||
dashboard.Should().Contain("up{job=\\\"edge-nodes\\\",instance=\\\"pirelay\\\"}");
|
|
||||||
dashboard.Should().Contain("signal_relay_writes_total");
|
|
||||||
dashboard.Should().Contain("signal_schedule_fires_total");
|
|
||||||
dashboard.Should().Contain("signalcontrol_screen_saver_enabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void StatefulSets_WithVolumeClaimTemplates_MustDeclareFilesystemDefaults()
|
public void StatefulSets_WithVolumeClaimTemplates_MustDeclareFilesystemDefaults()
|
||||||
{
|
{
|
||||||
@@ -836,16 +657,6 @@ public sealed class FleetManifestLintTests
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool EnvSecretOptional(YamlMappingNode container, string name)
|
|
||||||
{
|
|
||||||
return string.Equals(
|
|
||||||
EnvMapping(container, name) is { } env
|
|
||||||
? ManifestNodeExtensions.Scalar(env, "valueFrom", "secretKeyRef", "optional")
|
|
||||||
: null,
|
|
||||||
"true",
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static YamlMappingNode? EnvMapping(YamlMappingNode container, string name)
|
private static YamlMappingNode? EnvMapping(YamlMappingNode container, string name)
|
||||||
{
|
{
|
||||||
return ManifestNodeExtensions.MappingSequence(container, "env")
|
return ManifestNodeExtensions.MappingSequence(container, "env")
|
||||||
@@ -1081,22 +892,6 @@ internal sealed record ManifestDocument(
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// MainContainerMappings excludes initContainers. Use this when asserting
|
|
||||||
// properties of the primary container (env, image, volumeMounts) where an
|
|
||||||
// initContainer would be a false-positive match — e.g. the GitHub runner
|
|
||||||
// image's `setup-runner-home` initContainer should not count toward the
|
|
||||||
// single-container assertions on the runner deployments.
|
|
||||||
public IReadOnlyList<YamlMappingNode> MainContainerMappings()
|
|
||||||
{
|
|
||||||
var podSpec = PodSpec();
|
|
||||||
if (podSpec is null)
|
|
||||||
{
|
|
||||||
return Array.Empty<YamlMappingNode>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ManifestNodeExtensions.MappingSequence(podSpec, "containers").ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<ContainerSpec> ContainerSpecs()
|
public IReadOnlyList<ContainerSpec> ContainerSpecs()
|
||||||
{
|
{
|
||||||
return ContainerMappings()
|
return ContainerMappings()
|
||||||
|
|||||||
Reference in New Issue
Block a user