Compare commits

...

216 Commits

Author SHA1 Message Date
Andrew Stoltz
cebd934872 deploy(php): roll non-root GX10 operator image 2026-06-17 05:22:36 -05:00
Andrew Stoltz
8d55ca1566 deploy(mysql): roll non-root GX10 operator image 2026-06-17 04:34:28 -05:00
Andrew Stoltz
b11f26b963 deploy(mysql): roll non-root GX10 web image 2026-06-17 04:08:23 -05:00
Andrew Stoltz
aa0525331d deploy(updater): roll non-root GX10 image 2026-06-17 03:15:35 -05:00
Andrew Stoltz
9ce18e4acc fix(irc): inject GX10 cloak keys from Secret 2026-06-17 02:39:55 -05:00
Andrew Stoltz
11f32f1a6e deploy(dns): add GX10 fc-dns app 2026-06-17 02:12:40 -05:00
Andrew Stoltz
083e7f41cd fix(fc-php): restore missing IngressRoute + TLS cert (php-web 404 on GX10)
php.iamworkin.lan returned 404 on every path: the GX10 GitOps capture grabbed
fc-php's deployment/service but NOT its IngressRoute (chicken-egg — php wasn't
routed at capture time), so Traefik matched no route. Pod is 1/1 Running 37h —
the 404 was pure missing-route, confirmed by diffing against the healthy sibling
mysql-web (which has its IngressRoute).

Mirrors the mysql-web / fc-network pattern: a cert-manager Certificate (step-ca-acme
ClusterIssuer) to mint php-web-tls + an IngressRoute Host(php.iamworkin.lan)->php-web:5400.
Additive only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 01:57:47 -05:00
Andrew Stoltz
336c4a6ec0 deploy(signage): roll GX10 F2 image 2026-06-17 01:25:04 -05:00
Andrew Stoltz
415fec9e4d gx10-gitops: deploy-loop proof — mark knowledge svc managed-by gx10-argocd 2026-06-16 22:33:40 -05:00
Andrew Stoltz
6c0be8563d gx10-gitops: capture live manifests for 32 product namespaces (ArgoCD adoption source) 2026-06-16 22:24:23 -05:00
Andrew Stoltz
0218b1f8b6 gx10-gitops: pilot — capture live knowledge manifests (adoption source) 2026-06-16 22:18:20 -05:00
Andrew Stoltz
4b58b0ca5f deploy: align gateway key field 2026-06-16 21:08:03 -05:00
Andrew Stoltz
bd8adb2188 deploy: add MCP gateway for Agent Zero 2026-06-16 21:01:52 -05:00
Andrew Stoltz
d32abd62c8 deploy(chat): chat-web v20260616-circuit-mood-5711f2d
Ships the Blazor circuit-resilience + mood + telemetry fixes to live chat:
- FcAiChat reconnect-resync (stuck _generating after a circuit drop)
- fc-blazor-start.js client serverTimeout 60s (fewer spurious 1006 reconnects)
- ChatToolVisibility (tool plumbing hidden in personality chat)
- mood empathy fix (avatar no longer "excited" on bad news)
- fc-circuit-telemetry.js + /api/clientlog sink (kubectl-readable circuit data)

Image built on noc1 + imported to rke2-server + rke2-agent1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:29:01 -05:00
Andrew Stoltz
204001a89d deploy(dns): pin current DNS image 2026-06-16 11:45:13 -05:00
Andrew Stoltz
6950010ea4 ops(github-runner): scale tts-reader runner to 0 (crash-looping, memory relief)
The github-runner-tts-reader pod was crash-looping (329 restarts) and consuming
memory on the over-pressured old rke2 cluster (rke2-agent1 ~81%), contributing
to Blazor SignalR circuit drops on ttsreader/chat. It provides no working CI in
this state. Set replicas: 0 so ArgoCD stops re-creating it; restore to 1 once
the runner is fixed or CI moves to a working host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 11:27:40 -05:00
Andrew Stoltz
b28ab73a19 deploy(ttsreader): pin TT-3 endpoint fix image 2026-06-16 05:33:43 -05:00
Andrew Stoltz
09398d451f deploy(ttsreader): pin TT-3 plane health image 2026-06-16 05:15:48 -05:00
Andrew Stoltz
3a7978ab1f deploy(dns): pin DN-3b drift image 2026-06-15 20:56:30 -05:00
Andrew Stoltz
c0bfcb46fa deploy(dns): pin DN-3 PowerDNS image 2026-06-15 20:33:18 -05:00
Andrew Stoltz
ebbf501038 deploy(dns): pin DN-2 MCP bridge image 2026-06-15 20:15:42 -05:00
Andrew Stoltz
d4f24f6f43 deploy(dns): wire MCP transport key 2026-06-15 19:58:52 -05:00
Andrew Stoltz
9f4805f1d6 deploy(dns): pin DN-2 entity CRUD image 2026-06-15 19:52:42 -05:00
Andrew Stoltz
b9a81fb4c0 deploy(dns): pin DN-1 rate-limit image 2026-06-15 19:07:56 -05:00
Andrew Stoltz
a4ccd30429 deploy(chat): require intranet fallback citation image 2026-06-15 18:25:19 -05:00
Andrew Stoltz
09b22e32c2 deploy(chat): pin activation hardened citation image 2026-06-15 18:21:54 -05:00
Andrew Stoltz
5bb136554d deploy(chat): pin citation card fallback image 2026-06-15 18:16:15 -05:00
Andrew Stoltz
485710230b deploy(chat): pin citation card image 2026-06-15 18:06:08 -05:00
Andrew Stoltz
f016375419 deploy(chat): pin citation route fix image 2026-06-15 17:50:21 -05:00
Andrew Stoltz
fc64638029 deploy(chat): pin citation fallback fix-forward 2026-06-15 17:32:39 -05:00
Andrew Stoltz
6b751b0fbe deploy(chat): pin citation fallback image 2026-06-15 17:26:03 -05:00
Andrew Stoltz
a03dbe166d deploy(library): pin RL5 fine action image 2026-06-15 15:56:20 -05:00
Andrew Stoltz
6febe1fdb3 deploy(dns): enable production auth profile 2026-06-15 15:08:03 -05:00
Andrew Stoltz
40fd35ba44 deploy(chat): pin CH-6 presence image 2026-06-14 19:26:31 -05:00
Andrew Stoltz
17654835e7 gx10/platform: step-ca-acme issuer + Traefik HelmChart (migration platform layer)
Bootstrap manifests for the GX10 cluster platform layer (NUC->GX10 migration).
Direct-applied to GX10 + LIVE: step-ca-acme ClusterIssuer Ready (ACME->noc1 step-ca),
Traefik v3.6.10 via RKE2 HelmChart CRD at MetalLB VIP 10.0.57.202 (prod-pool, temp
parallel-run; no clash with live old .200). Under gx10/ NOT apps/* to avoid the old
ApplicationSet auto-deploying GX10 manifests to the OLD cluster.
2026-06-14 18:06:25 -05:00
Andrew Stoltz
63b8d4b667 Deploy Chat regroup CH-3 image 2026-06-14 18:01:43 -05:00
Andrew Stoltz
2c12f35f75 agent-zero: fix fc_dms netpol egress port (8080 = pod targetPort, not svc 80)
NetworkPolicy matches the destination POD port. dms-web svc:80 -> containerPort
8080, so the egress must allow 8080 (the fc-chat rule already allows 80+8080,
which is why chat worked and dms timed out). Add 8080 to the fc-dms egress.
2026-06-14 16:25:25 -05:00
Andrew Stoltz
e33fe81823 agent-zero: connect fc_dms MCP (product-manager fan-out, first server)
AZ only had fc_chat (chat-session) + fc_knowledge (RAG) — so it had no product
capabilities (the 'mysql manager' gap). Wire fc_dms (dynamic message signs, ~13
tools): OnePasswordItem dms-mcp-keys (1P 'FlowerCore DMS MCP Keys' field credential)
-> DMS_MCP_API_KEY -> X-Api-Key; builder adds fc_dms; netpol egress fc-dms:80.
Proven: dms-web/mcp returns 200 with this key. presentations/messageboard/
segmentdisplay/telephony 1P MCP-key items exist for the same pattern; mysql+signage
need 1P items provisioned first (mysql/mcp 401s with no key). Watch context budget.
2026-06-14 16:19:34 -05:00
Andrew Stoltz
ef6afdd577 fc-llm-bridge: repoint Ollama to GX10 NodePort (fix AZ MTU black-hole)
The PROD-VLAN VIP 10.0.57.201 MTU-black-holes Agent Zero's ~150KB requests
(full prompt + 108 MCP tools) -> connection reset mid-stream -> AZ 'same message
again' loop. Switch FlowerCore__Chat__OllamaBaseUrl to the INFRA-VLAN NodePort
10.0.56.14:30976 (same VLAN as the old cluster, carries 150KB fine). Verified:
150KB POST = 200 via NodePort, times out via VIP. NodePort pinned to 30976 on GX10.
2026-06-14 15:12:05 -05:00
Andrew Stoltz
62ca7dacf6 telephony: deploy ARI abort-fix image v20260614-arifix; drop 3600s band-aids
Image -> v20260614-arifix (Telephony 86ff0d0: ReceiveAsync no longer cancelled).
Remove the WebSocketKeepAliveTimeoutSeconds/WebSocketReceiveTimeoutSeconds=3600
band-aids; the code now disables the pong deadline by default and ignores the
receive timeout (liveness = keepalive ping + WebSocketException/Close).
2026-06-14 14:36:11 -05:00
Andrew Stoltz
d03a92407d gx10/tts: persist Piper /tts source + manifest (telephony TTS port baseline)
Dockerfile (linux/arm64, en_US-amy-medium baked), tts_service.py (16kHz/16-bit/mono
WAV, numpy resample 22050->16000), gx10-tts.yaml (CPU NodePort 30850, no GPU request),
README (build/import/cutover/verify on the GX10 cluster).
2026-06-14 14:14:59 -05:00
Andrew Stoltz
e4d1735d35 telephony: make TTS cutover EFFECTIVE via Tts__PiperUrl env (overrides configmap)
Root cause: the live deploy carried env Tts__PiperUrl=edge1 (drifted, not in git)
which shadows appsettings Tts.PiperUrl. Codify Tts__PiperUrl=GX10 + Ari__ env to
match live so git is source-of-truth; the configmap edit alone was inert.
2026-06-14 14:12:02 -05:00
Andrew Stoltz
15edcb7c71 telephony: cut TTS over to GX10 (10.0.56.14:30850, amy-medium); keep edge1 warm
- Tts.PiperUrl edge1 10.0.57.17:8500 -> GX10 NodePort 10.0.56.14:30850
- add netpol egress to GX10 TTS; keep edge1 egress as rollback target
- DefaultEngine piper / SampleRate 8000 unchanged (sln16 16kHz path)
2026-06-14 14:01:50 -05:00
Andrew Stoltz
284ca84166 agent-zero: GX10 system prompt rewrite (tool-calling + RAG rules, strip dead lanes)
Sync the bluejay-profile ConfigMap's embedded system_prompt.md with the
rewritten scripts/agent-zero/agents/bluejay/system_prompt.md: Ollama section
-> GX10 hub (VIP 10.0.57.201, GB10/121GiB); model table with tool-calling
flags (qwen2.5 = tools, gemma3 = 400-on-tools/vision-only, nomic = embed);
new 'Models & Tool-Calling' + 'Knowledge & RAG' rule blocks; stripped dead
WSL/R9700/.132/host.docker.internal/port-30050 lanes; de-pinned test counts;
'Blu' team is persona vocabulary not a fixed team. Personality preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:40:25 -05:00
Andrew Stoltz
7a86c40cf1 fix(telephony): ARI receive timeout 45s->3600s — the real false-abort root cause
Cancelling ClientWebSocket.ReceiveAsync via CancellationToken ABORTS the
socket (a half-read WS frame can't resume). The per-iteration
iterationCts.CancelAfter(WebSocketReceiveTimeoutSeconds) therefore aborted a
healthy idle ARI WebSocket every 45s (state=Aborted), not the keepalive pong
(proven: loop persisted after pong-timeout 15s->3600s). A large receive
timeout lets ReceiveAsync block harmlessly while the PBX is idle; real drops
still surface immediately as WebSocketException -> reconnect. Proper code fix
(stop using CancelAfter on the receive) tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:04:13 -05:00
Andrew Stoltz
de5c9f39fd deploy(devicemgmt): pin regroup web image 2026-06-14 12:52:30 -05:00
Andrew Stoltz
d5311de676 fix(telephony): stop ARI WebSocket false-abort loop (pong-timeout 15s->3600s)
Asterisk res_http_websocket does not reliably answer client PING frames
with PONG, so .NET KeepAliveTimeout (default 15s) aborted a healthy idle
ARI WebSocket every ~45s (ping@30s + pong-wait@15s), dropping StasisStart
events so the *100 IVR intermittently answered with no audio. Generous pong
timeout stops the false aborts; genuine drops still caught by the 45s
receive-timeout state re-check and TCP-level WebSocketException.

Surfaced by FlowerCore.Telephony.SipTests Call_Star100_ReceivesAudibleAudioStream
(0 RTP packets while ExtToExt RTP-hook passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:50:12 -05:00
Andrew Stoltz
7b4f57bb97 deploy(updater): pin regroup web image 2026-06-14 12:45:39 -05:00
Andrew Stoltz
c569c05ad7 deploy(retail-library): roll regroup web images 2026-06-14 12:38:57 -05:00
Andrew Stoltz
fc8297041a deploy(fc-chat): roll effective-prompt debug reveal v20260614-debugreveal-d389e4b
Influence Audit panel now surfaces the per-turn effective prompt
(RagContextSnapshot) as an operator/debug row. FlowerCore.Chat d389e4b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 12:33:37 -05:00
Andrew Stoltz
e1554757e8 deploy(fc-chat): roll user-bubble prompt-leak fix v20260614-bubblefix-37f57b0
Stored/displayed user message is now the raw prompt; injected scaffolding
(mood contract + guidance + memory) goes to the model via ragContext as a
system message and is captured in RagContextSnapshot for debug.
FlowerCore.Chat 37f57b0 + FlowerCore.Common 4d741b3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 03:15:26 -05:00
Andrew Stoltz
0c8e6ee8ab agent-zero(models): tool-capable qwen2.5 on GX10 via fc-llm-bridge (Wiring A)
Agent Zero's agentic tool-loop ran on cloud Anthropic Sonnet (the bridge's
Anthropic key is currently 401) + gemma3:4b util (gemma3 returns 400 "does not
support tools" — fatal for the loop). Repoint the bridge ModelRouter tiers:
Balanced -> Ollama qwen2.5:14b (AZ chat) and Cheap -> qwen2.5:7b (AZ util), both
on the GX10 VIP 10.0.57.201 (already the bridge OllamaBaseUrl). Env-only, no
rebuild; Wiring A keeps the budget ledger + cache. Also: AZ chat ctx -> 32768,
browser -> qwen2.5:7b (text/tool-capable, vision off), AGENT_NAME -> "Blue Jay"
(the NUC role is retired). qwen2.5:7b + :14b pulled + warm-pinned on the GX10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 02:38:17 -05:00
Andrew Stoltz
9d5a1cce97 deploy(fc-chat): roll mood-signal build v20260614-moodsignal-a606892
Workstream A: set_mood structured signal replaces leaky [mood:X] text
(FlowerCore.Chat a606892). Image built + imported to rke2-server and
rke2-agent1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 02:21:47 -05:00
Andrew Stoltz
e0460bd881 infra(ai): consolidate fleet Ollama consumers onto GX10 VIP 10.0.57.201
Repoints fc-chat, fc-ttsreader, knowledge, fc-llm-bridge (off the slow edge1
Pi5 10.0.57.17) and intranet (off the reimaged BLUEJAY-AI test laptop
10.0.56.132) to the GX10 (DGX Spark / GB10) Ollama over the PROD MetalLB VIP
10.0.57.201. GX10 serves gemma3:12b/gemma3:4b/qwen2.5:1.5b/nomic-embed-text/
llama3.2:1b on local NVMe, warm-pinned (keep_alive=-1).

fc-chat default model qwen2.5-coder:7b -> gemma3:12b (the coder model won't
pull reliably on the GX10; gemma3:12b is the warm fleet default + a better
general-chat model). Other consumers keep their exact models. Inline comments
referencing edge1/BLUEJAY-AI are now historical; the values are the GX10 VIP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 00:54:36 -05:00
Robot
303c450bc9 Cl-5: Admin console infra finding — rides DM.Web (zero new infra)
Audit of apps/fc-devicemgmt/ confirms the admin/helpdesk console needs NO new
infra: the existing host-matched IngressRoute (devices.iamworkin.lan, no path
constraint) + step-ca-acme Certificate already cover admin routes served under
FlowerCore:PathBase (ADR-204 routes-inside-DM.Web). ADMIN-CONSOLE-INFRA.md
records the finding + the open Q-MP question (distinct admin hostname vs PathBase
path) with the exact 3-step add if a separate host is later chosen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:22:16 -05:00
Andrew Stoltz
9dd170a9ac deploy(chat): route wave5 chat ollama to edge1 2026-06-13 22:59:18 -05:00
Andrew Stoltz
50a3ee5e8e deploy(chat): enable helpdesk sentiment escalation 2026-06-13 22:51:21 -05:00
Andrew Stoltz
87de007a7f deploy(wave5): roll deep-regroup product images 2026-06-13 22:48:31 -05:00
Andrew Stoltz
77df227425 deploy(intranet): roll product docs image 2026-06-13 20:23:08 -05:00
Andrew Stoltz
a65f422147 infra(gated): stage authentik-tenant-mapping-sync CronJob (Au-3, suspended)
Gated substrate (Cl2-4 / Cl-infra-3) — outside apps/ so the ApplicationSet
will not deploy it, and spec.suspend: true. Reconciles the 1Password
tenant-mapping doc into Authentik groups via Connect REST. Activate at Au-3
public-go (un-suspend + materialize the script ConfigMap). Pairs Codex Cx2-7.
Canonical script: FlowerCore.Notes/scripts/authentik/authentik-tenant-mapping-sync.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 17:34:29 -05:00
Andrew Stoltz
6cb54abfa7 perf(intranet): repoint embed backend to BLUEJAY-AI GPU (10.0.56.132) for faster bulk embed
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 17:20:28 -05:00
Andrew Stoltz
d06637b747 deploy(cx2-1): roll chat and intranet wave images 2026-06-13 17:18:11 -05:00
Andrew Stoltz
387097485e infra(public-tls): add gated Let's Encrypt issuers + tenant NetworkPolicy substrate
Cl-infra-2 (deep-regroup 2026-06-13). LE staging+prod ClusterIssuers (HTTP-01
via Traefik, DNS-01 stub) + a per-tenant default-deny NetworkPolicy template,
under gated/public-tls/ OUTSIDE apps/ so the ApplicationSet does NOT auto-apply
them (an applied ACME ClusterIssuer registers an account immediately). Internal
*.iamworkin.lan TLS stays on step-ca. Inert until the operator opens the
web-hosting public-exposure gate (R-1; 14/14 blockers red). Pairs with Codex
Wh-C1 (hybrid public TLS) + Wh-C2 (isolation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:06:31 -05:00
Andrew Stoltz
b098604a6f fix(intranet): point IntranetSearch embed backend at edge1 by IPv4 (10.0.57.17)
The hostname edge1.iamworkin.lan resolves to an unroutable IPv6 from cluster
pods and the CoreDNS *.iamworkin.lan template maps it to the Traefik VIP, so
the corpus indexer failed every embed with "No route to host". edge1's IPv4
(10.0.57.17, PROD VLAN) is pod-routable and has nomic-embed-text; an in-pod
embed test returned real vectors. This makes the now-enabled notes-md/notes-html
indexes actually populate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 11:55:26 -05:00
Andrew Stoltz
110d6fd1e0 infra(intranet): mount Notes docs corpus + enable IntranetSearch indexer
Cl-infra-1 (deep-regroup 2026-06-13). Adds a notes-corpus-clone initContainer
(shallow git clone of bluejay/FlowerCore.Notes into an emptyDir at
/srv/flowercore-notes) + a notes-corpus-sync sidecar (30-min pull) and flips
IntranetSearch__Enabled false->true so the previously doubly-disabled indexer
has a corpus to index (768 md + 108 html under docs/).

- Trailing-dot FQDN gitea-clusterip.gitea.svc.cluster.local. bypasses a CoreDNS
  *.iamworkin.lan template that mis-resolves the in-cluster service name to the
  Traefik VIP for musl / ndots:5 pods (search-domain appending).
- Cred via gitea-corpus-cred secret (canonical 1P bluejay read cred, created
  imperatively in-ns; mirrors the gitea-flowercore-notes argocd repo-cred pattern).
- First-boot bulk embed runs in background via edge1 Ollama; /health stays Ready.

Pairs with Codex In-1 (intranet app-side reindex endpoint + SemaphoreSlim).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 11:48:24 -05:00
Andrew Stoltz
6b2e6a61d0 deploy(dns): roll hosting quota image 2026-06-13 02:06:40 -05:00
Andrew Stoltz
503685d0f5 deploy(devicemgmt): roll windows update policy image 2026-06-13 00:46:30 -05:00
Andrew Stoltz
05f37df5d2 deploy(devicemgmt): roll sqlite-safe trust bundle image 2026-06-13 00:12:13 -05:00
Andrew Stoltz
f3afa64c5d deploy(devicemgmt): roll edge network enrollment image 2026-06-13 00:04:44 -05:00
Andrew Stoltz
b4a1cb63f0 deploy: roll dns tenant repeat fix image 2026-06-12 22:54:30 -05:00
Andrew Stoltz
d95aa453ea deploy: roll dns web repeatable tenant image 2026-06-12 22:45:13 -05:00
Andrew Stoltz
0bbba2739c deploy: roll devicemgmt ollama gateway image 2026-06-12 22:16:15 -05:00
Andrew Stoltz
99f49c1b75 deploy: roll devicemgmt patch ledger image 2026-06-12 21:55:07 -05:00
Andrew Stoltz
14a0e87513 deploy: roll devicemgmt sqlite enrollment fix 2026-06-12 21:32:49 -05:00
Andrew Stoltz
d2e8b5f4a8 deploy: roll devicemgmt enrollment image 2026-06-12 21:26:22 -05:00
Andrew Stoltz
861ed42e2c deploy: roll e4 conformance web images 2026-06-12 19:48:07 -05:00
Andrew Stoltz
605073c299 deploy(devicemgmt): roll e3 ollama policy pack image 2026-06-12 19:27:08 -05:00
Andrew Stoltz
346b287a3d chore(fc-devicemgmt): bump web to v20260612-hubfix-afa9f4d (DeviceAgentHub ct-param enrollment outage fix)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:10:37 -05:00
Andrew Stoltz
6bd02f5781 chore(worldbuilder): deploy C7 next arc image 2026-06-12 18:08:54 -05:00
Andrew Stoltz
2a2b416d12 chore(dns): deploy C4 tenant onboarding image 2026-06-12 17:47:37 -05:00
Andrew Stoltz
d3ae09865a chore(chat): deploy C8 action execution image 2026-06-12 17:24:55 -05:00
Andrew Stoltz
637a8ffd69 chore(devicemgmt): deploy C13 policy web image 2026-06-12 17:01:37 -05:00
Andrew Stoltz
6ab232761d chore(ttsreader): bump fc-ttsreader-web to v20260612-ui-conformance (FC UI conformance D5)
Gold PWA primary CTA (mobile-button--primary blue->gold cascade fix) + About
operator jump-links / honest update-status / license (FcAboutPanel contract).
Image built + imported to rke2-server + rke2-agent1; pin so ArgoCD adopts the
new tag instead of reverting the kubectl set image.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:51:57 -05:00
Andrew Stoltz
bfe42cf44e feat(fc-network): add FlowerCore.Network app (read-only pfSense plane, ADR-189)
Stand up the pfSense automation plane (Phase 0, read-only) on RKE2 as an
ArgoCD-managed workload at network.iamworkin.lan.

- namespace fc-network
- Deployment fc-network-web: localhost/fc-network-web:v20260612-0b5b049,
  imagePullPolicy Never, port 5340, /healthz probes, runAsNonRoot 1654 +
  readOnlyRootFilesystem, RWO-safe RollingUpdate (maxSurge 0/maxUnavailable 1),
  auth gate-OFF, SQLite + snapshot-store + intended-model paths under /data.
- PVC fc-network-web-data (longhorn, 2Gi): SQLite index + on-box snapshot store
  (full-fidelity raw config.xml stays on-box; service surfaces redacted only).
- Service (ClusterIP 80 -> 5340), Certificate (ClusterIssuer step-ca-acme),
  IngressRoute (network.iamworkin.lan, all methods — POST ingest is local-only).
- kustomization.yaml for local previews / single-app validation.

The ApplicationSet git generator picks this up as infra-fc-network; if it lags,
the Application is applied manually (documented pattern).
2026-06-12 14:21:45 -05:00
Andrew Stoltz
bf96f7b9a2 deploy(devicemgmt): use rwo-safe rolling strategy 2026-06-12 12:42:20 -05:00
Andrew Stoltz
8be054f99a deploy(devicemgmt): use recreate for sqlite pvc rollout 2026-06-12 12:38:05 -05:00
Andrew Stoltz
6abb2d6408 deploy(devicemgmt): roll L8 web image 2026-06-12 12:33:15 -05:00
Andrew Stoltz
8e2c960be3 deploy(dns): align l4 image and auth gate 2026-06-12 12:10:23 -05:00
Andrew Stoltz
c482b66187 deploy(worldbuilder): bump image to v202606121657-35aaa2c-gpu (L2 UI sweep)
Ships the L2 pilot UI sweep to worldbuilder.iamworkin.lan: the dashboard
fc-component fix (missing-styles), ComfyUI local detection, and the rebuilt
About page. Image imported to rke2-server (10.0.56.11) + rke2-agent1
(10.0.56.12). rke2-agent2/10.0.56.13 is retired and was not used.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:01:16 -05:00
Andrew Stoltz
bacb756173 feat(fc-desktop): OnePasswordItem CRD for remotedesktop-oidc-client (L9 flip-readiness, gate stays OFF)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 11:31:07 -05:00
Andrew Stoltz
8a576c95ed deploy(fc-ttsreader): v20260612-readalong-corrections
TtsReader master@355a9c6: global pronunciation correction memory
(/corrections + REST/MCP), public read-along embed manifests with
fc-reader single-file cue windows (Common@639e233), mood gathering
timelines, listening-note capture, approved-only render contract fix,
and Codex Phase 14.2 rehearsal cue sheets (#42). Tests 1609/1609.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 10:07:37 -05:00
Andrew Stoltz
41c2243f09 deploy(intranet): roll screenshot metadata image 2026-06-12 01:15:23 -05:00
Andrew Stoltz
c21e602e4d deploy(intranet): roll page reading profile image 2026-06-12 00:34:21 -05:00
Andrew Stoltz
9f6b71c400 deploy(intranet): roll remotedesktop api ref image 2026-06-11 19:23:07 -05:00
Andrew Stoltz
26f90acf1f deploy(intranet): roll platform badge image 2026-06-11 18:59:25 -05:00
Andrew Stoltz
ab00d22657 deploy(worldbuilder): roll route fix image 2026-06-11 16:17:17 -05:00
Andrew Stoltz
c1a43c64b3 deploy(worldbuilder): enable live gpu backend 2026-06-11 16:05:40 -05:00
Andrew Stoltz
7103658342 deploy(intranet): roll regroup follow-through image 2026-06-11 15:58:12 -05:00
Andrew Stoltz
6b12b2bb49 deploy(intranet): roll operator depth image 2026-06-11 15:06:08 -05:00
Andrew Stoltz
a4c9e44a36 fix(runners): disable self-update in k8s pods 2026-06-11 14:57:00 -05:00
Andrew Stoltz
9674a9555e deploy(intranet): roll article depth image 2026-06-11 14:27:24 -05:00
Andrew Stoltz
318252da76 deploy(devicemgmt): roll healthz web image 2026-06-11 14:27:14 -05:00
Andrew Stoltz
3798b7c00e deploy(devicemgmt): enable web runtime 2026-06-11 14:21:51 -05:00
Andrew Stoltz
2707f1ae1e deploy(intranet): roll regroup catalog image 2026-06-11 12:32:40 -05:00
Andrew Stoltz
a7e7c1ae72 deploy(intranet): roll content quality image 2026-06-10 20:13:56 -05:00
Andrew Stoltz
c8df788d72 deploy(intranet): roll webmail health image 2026-06-10 19:15:44 -05:00
Andrew Stoltz
b1a4d7120e deploy(intranet): roll registry health image 2026-06-10 19:10:31 -05:00
Andrew Stoltz
4b57b8e939 fix(intranet): align search deploy config 2026-06-10 19:01:08 -05:00
Andrew Stoltz
70f36c546b deploy(intranet): roll hardening image 2026-06-10 18:58:09 -05:00
Robot
cdbddd71af fc-devicemgmt: stage fresh web image v20260610-bluejay (master 1614fce)
Image built from current DM master (network/BT command plane + Blue Jay
UI.Components restyle) and imported on rke2-server + rke2-agent1.
Deployment stays parked at replicas: 0 — gap 1 is wider than previously
noted (the fc-mysql Operator deployment itself is absent, so instance
CRDs would not reconcile) and gap 2 (1P runtime item) is still open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:57:43 -05:00
Andrew Stoltz
81ac1f3e4f authentik: align volumeClaimTemplates TypeMeta with SSA-created live object
StatefulSet/authentik-postgres has been eternally OutOfSync since ~Sprint 65
even though 'kubectl diff --server-side --field-manager=argocd-controller'
shows zero real change. The STS was created via ServerSideApply, so the live
object carries apiVersion/kind inside volumeClaimTemplates[]; git omitting
them makes ArgoCD's normalized diff disagree forever. Declare them in git.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:18:29 -05:00
b842738a0e Merge pull request 'Sprint 63 Cx-10: align hardening probe paths with live routes' (#44) from codex/s63-cx10 into main
Sprint 63 Cx-10 live-proof fix after Traefik curls found three stale probe-path annotations. Local lint 100/100; git diff --check clean; no Gitea statuses attached.
2026-06-05 03:02:14 +00:00
Andrew Stoltz
f0cb7a5e81 fix(hardening): align probe-path annotations with live health routes 2026-06-04 22:01:04 -05:00
ac0f665323 Merge pull request 'Draft: Sprint 62 Cx-10 broader exposure hardening' (#43) from codex/s62-cx10 into main
Sprint 63 Cx-10 reconcile-first merge after local lint proof: 100/100 passed, no Gitea statuses attached, CRLF diff check clean.
2026-06-05 02:51:37 +00:00
Andrew Stoltz
c4b08f41ab feat(infra): prestage broader app exposure hardening 2026-06-04 18:14:22 -05:00
Andrew Stoltz
417d3830ae test(lint): reconcile baseline infra assertions 2026-06-04 18:02:32 -05:00
cb4ea13e7a monitoring: mirror Sprint 60 probe coverage
Merged on local lint plus live noc1 Prometheus /api/v1/rules proof.
2026-06-04 18:19:47 +00:00
Andrew Stoltz
a3cd67d6bb monitoring: mirror Sprint 60 probe coverage 2026-06-04 13:15:18 -05:00
Andrew Stoltz
81a3ddac4c fix(auth): mark OIDC healthz probes anonymous 2026-06-04 11:03:20 -05:00
300f8ad546 fix(monitoring): probe OIDC-safe health routes
Sprint 58 Cx-12. Rebased over OIDC GitOps main; YAML parse and focused bluejay-infra lint tests passed.
2026-06-04 06:45:34 +00:00
fe38c2641f Merge pull request 'fix(auth): deploy distribution root anonymous image' (#38) from codex/s58-distribution-root-anon-gitops into main 2026-06-04 06:20:09 +00:00
Andrew Stoltz
3b40dfb185 fix(auth): deploy distribution root anonymous image 2026-06-04 01:19:16 -05:00
103878671c Merge pull request 'fix(auth): deploy Distribution OIDC image tag' (#37) from codex/s58-oidc-proper into main 2026-06-04 06:05:15 +00:00
Andrew Stoltz
36039c1335 fix(auth): deploy distribution oidc image tag 2026-06-04 01:04:44 -05:00
2a66109f13 Merge pull request 'feat(auth): adopt OIDC GitOps for DNS Distribution Media' (#36) from codex/s58-oidc-proper into main 2026-06-04 05:52:56 +00:00
Andrew Stoltz
933fea89d1 feat(auth): adopt oidc apps in gitops 2026-06-04 00:49:36 -05:00
Andrew Stoltz
13f9bb7710 fix(distribution): revert OIDC enforcement — enabling it gated /healthz probe (service down)
Flipping Auth__Enabled=true gated the /healthz readiness probe (302->NotReady->
no endpoints->distribution.iamworkin.lan down, healthz=000). Classic
feedback_k8s_probes_behind_auth_middleware. Revert to false (OIDC env block kept,
gate off) to restore service. Proper fix (AllowAnonymous /healthz + CA-trust +
idempotent Editions seed + OIDC-challenge wiring + browser-proof) -> falcon OIDC lane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:47:29 -05:00
Andrew Stoltz
9a58fd2af6 oidc: flip enforcement ON for knowledge + distribution (no-live-proof, fix-forward)
Operator 2026-06-04: nothing is production yet, flip OIDC + fix-forward (no
browser-proof gate). knowledge: Auth__Enabled false->true (OIDC env already
wired). distribution: add OIDC env block (Authority/Audience/ClientId=distribution,
ClientSecret from distribution-oidc-client) + Enabled=true; public read/entitlement
+ Method() allowlist stay open (OIDC gates admin only). Clients already provisioned
(secrets present). ArgoCD deploys both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:38:48 -05:00
Andrew Stoltz
404d884863 Adopt live Library Retail AiStation web apps 2026-06-03 20:24:32 -05:00
f4bd90f805 Merge pull request #33 from codex/s56-monitoring-coverage
fix(monitoring): repoint pirelay scrape to signalcontrol
2026-06-04 01:22:49 +00:00
Andrew Stoltz
67d67ab73d fix(monitoring): repoint pirelay scrape to signalcontrol 2026-06-03 20:20:36 -05:00
Andrew Stoltz
f7d41cdc60 revert: drop fc-library manifest — Library.Web already deployed live (41h)
Library.Web is already running + serving at library.iamworkin.lan (root=200,
healthz=200), deployed manually 41h ago (image fc-library-web:v20260602-...,
PVC library-web-data holding the live SQLite DB). My from-scratch manifest used
a different PVC name (library-data) which ArgoCD would attach as a fresh empty
volume, orphaning the live DB. Adopting the live deploy into GitOps is a
separate careful task. Not disturbing a working deployment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:30:23 -05:00
Andrew Stoltz
2c0afc28e4 deploy(fc-library): add Library.Web internal-host deployment
From-scratch .Web deploy at library.iamworkin.lan (operator-authorized 2026-06-03).
Cloned from the worldbuilder pattern: Deployment + Service + Longhorn RWO PVC +
step-ca cert + Traefik IngressRoute. SQLite at /data/library.db, no OIDC, both
/health + /healthz probes. Image localhost/fc-library:v202606031925 imported to
both RKE2 nodes. DNS library.iamworkin.lan -> 10.0.56.200 already in pfSense.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:28:22 -05:00
Robot
ba5f5dd0fb deploy(knowledge): roll audit backfill fix 2026-06-03 18:24:22 -05:00
Robot
dc699da7b3 fix(knowledge): persist federation database on PVC 2026-06-03 18:17:31 -05:00
Robot
1e8bf54c6e deploy: roll Chat and Knowledge OIDC images 2026-06-03 18:13:09 -05:00
Andrew Stoltz
e2e93d482c Deploy TtsReader schema repair image
Co-Authored-By: Codex <codex@openai.com>
2026-06-02 22:00:15 -05:00
4319cc2b51 Merge PR #32: divoom pi deploy artifact manifests
Lands Divoom-as-DM-device and Divoom-TV Pi HDMI deploy artifacts for Cx-6.
2026-06-03 02:47:36 +00:00
Andrew Stoltz
2bf339ce51 Deploy TtsReader PR29 live proof image
Co-Authored-By: Codex <codex@openai.com>
2026-06-02 21:47:04 -05:00
Andrew Stoltz
5bdedfc5ae divoom: add pi deploy artifact manifests
Add source-controlled Puppet/Hiera contracts for edge2 Divoom-as-DM-device without replacing the live flowercore-divoom systemd deployment.

Add Divoom TV Pi HDMI systemd/Puppet deployment artifacts, LF shell-script guardrails, and focused lint coverage for the additive non-K8s deploy shape.

Co-Authored-By: Codex <codex@openai.com>
2026-06-02 21:45:27 -05:00
Andrew Stoltz
0307ae16ae monitoring(probe): signage/mysql/php blackbox probe / -> /healthz (K8s-target mirror)
Mirrors the live noc1 podman fix + Notes scripts/monitoring/prometheus.yml.
These services enforce OIDC bearer auth (FlowerCore__Auth__Enabled=true), so an
anonymous probe of / returns 401 -> false TraefikServiceDown. All three expose
anonymous /healthz=200. This noc-monitoring.yaml is the forward K8s-migration
target (not live); brings it in sync with the live config.
2026-06-02 01:09:57 -05:00
Andrew Stoltz
6c18f69cf2 mail: remove cert-manager Certificate (manage mail-tls via step-ca JWK + noc1 renew timer)
step-ca-acme only has an HTTP-01 (Traefik) solver, but mail.iamworkin.lan must resolve
to the dedicated MetalLB IP 10.0.56.202 (SMTP/IMAP), so HTTP-01 cannot validate (order
stuck pending since 2026-05-06; cert expired 2026-05-24). mail-tls is now issued from
step-ca's JWK 'admin' provisioner and auto-renewed by a systemd timer on noc1 that writes
the mail-tls secret directly. The secret + Deployment mount + webmail IngressRoute are
unchanged. Re-add a Certificate only if a DNS-01 solver is deployed for step-ca-acme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 15:55:38 -05:00
Andrew Stoltz
47e2256556 Deploy TtsReader correction bridge images 2026-05-31 12:35:45 -05:00
Andrew Stoltz
9d77f8ba0e fc-updater: disable loki audit sink 2026-05-31 11:34:12 -05:00
Andrew Stoltz
2f4be19c85 fc-updater: bump signing diagnostics image 2026-05-31 00:32:48 -05:00
Andrew Stoltz
2a62c40990 fc-updater: bump image to MSI installer surface 2026-05-30 23:31:48 -05:00
Andrew Stoltz
7be98e5efc Bump UpdateCenter image to hosted-service fix 2026-05-30 20:22:13 -05:00
Andrew Stoltz
a65b356c9d deploy(fc-updater): roll UC to v202605301823-a6c3354 (Phase 3 SQLite fixes)
Durable image bump for FlowerCore.Updater main a6c3354 (PRs #63-#66): hosted-service
+ request-path SQLite DateTimeOffset fixes, StopHost restored + per-tick resilience,
Shared.Settings 1.0.1. Image built + imported to rke2-server. Un-degrades the Phase-9
provenance verifier + settings poll (were stopped under the removed global Ignore mask).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 18:27:45 -05:00
Andrew Stoltz
08c17ef1b4 fc-updater: bump to v202605301703-296f350-fix2 (BackgroundServiceExceptionBehavior=Ignore so a hosted-service SQLite query crash can't stop the host)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 17:04:54 -05:00
Andrew Stoltz
06f2f002b7 fc-updater: bump image to v202605301657-296f350-fix1 (Shared.Settings SQLite poll fix)
The v202605301642-296f350-rework image crash-looped: FlowerCore.Shared.Settings SettingsDbPollHostedService
ran a DateTimeOffset Where/OrderBy on SettingsRecordChanges that SQLite can't
translate, and as a BackgroundService it stopped the host. Shared.Settings 1.0.1
materializes the change-log then filters/orders in memory; Updater Web bumped to 1.0.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:59:37 -05:00
Andrew Stoltz
7ac4a8b4b7 fc-updater: bump image to v202605301642-296f350-rework (ADR-179 rework live)
Deploy the current FlowerCore.Updater main (PRs #52-#61) to prod: MSI-first
packaging, beta gating + per-install tokens, interactive+bearer Authentik OIDC,
native installer apply, and the .fcsetup.exe retirement (DropReleaseInstallers
migration runs on the now-empty DB). Image pre-imported to rke2-server + agent1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:47:28 -05:00
Andrew Stoltz
90f2a86819 ops: trim load for degraded 2-node cluster (agent2 PSU dead)
Scale all github-runner deployments to 1 replica and halt the ci1
KubeVirt VM. With agent2 down (failed PSU) the cluster runs on two
passively-cooled NUCs; the ci1 8-vCPU VM drove agent1 to ~100C. Keep
total load trimmed until replacement hardware is in place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 13:47:13 -05:00
Andrew Stoltz
cbdefb2b23 Revert "ci1: expose WinRM/RDP/SSH ports on masquerade interface for Phase 2 bootstrap"
The port additions caused the new VMI to stick at phase=Scheduled with
reason=GuestNotRunning. The guest-console-log sidecar exited 1 and
qemu never started. Reverting to the working 9-day-stable shape until
the port-add path is verified in a non-production VM.

Phase 2 (Windows runner install + registration) needs an operator-
interactive virtctl-vnc session against the rebuilt VM, OR a separate
investigation of why this port-add tipped over the VM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 11:35:10 -05:00
Andrew Stoltz
1c36fe3a0a ci1: expose WinRM/RDP/SSH ports on masquerade interface for Phase 2 bootstrap
The Phase 1 VM has been Running for 9 days but Phase 2 (Puppet bootstrap +
runner registration) was deferred because the operator-interactive
virtctl-vnc path was the only way in. The masquerade interface listed
no exposed ports, so virtctl ssh and kubectl port-forward both hit
'no route to host' — qemu user-mode NAT does not forward inbound by
default.

Adding 5985 (WinRM HTTP) lets a kubectl port-forward + PowerShell
remoting path drive runner registration entirely from outside the VM.
3389 + 22 are reserved for desktop access via Guacamole or virtctl ssh
once OpenSSH Server is installed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 11:24:34 -05:00
Andrew Stoltz
2b420ce8a4 runners: fleet-wide right-size CPU requests from 500m to 100m
All 33 runner Deployments now request 100m CPU instead of 500m,
freeing roughly 50 idle pods × 400m = ~20 cores back to the cluster.
Observed CPU usage on idle runners is ~1m via kubectl top; the 500m
request was a 500× over-provision that was eating allocatable CPU
and blocking new workload scheduling — WorldBuilder runner could not
be scheduled even at the new 100m request because the pre-existing
fleet held the cluster at 99% requested.

Burst headroom preserved by limits.cpu: 2000m unchanged. TtsReader
keeps its 8Gi memory limit from the 2026-05-25 OOMKill fix; only
the CPU request line moves.

Recreate strategy on each deployment means a brief offline window
per runner during rollout; in-flight CI jobs complete on the
existing container before the new spec takes effect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:09:24 -05:00
Andrew Stoltz
5cbc1a06b1 runners: scale DM/AiStation.Linux/WorldBuilder down to 1 replica until cluster relieved
After cutting requests to 100m, 4 of 6 new pods scheduled and 2 stayed
Pending — cluster CPU REQUEST utilization is 49.6 of 48 allocatable cores
because the existing fleet of ~50 idle runners reserves 25.6 cores
(500m × ~50) for ~50m actual use. Single-replica per new repo gets the
service online without competing with in-flight CI from the rest of the
fleet.

When the broader fleet-wide request right-sizing pass lands
(500m → 100m on all idle runners would free ~20 cores), these can be
bumped back to 2 replicas if PR-CI backlog warrants it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:03:30 -05:00
Andrew Stoltz
9e7ee39b3a runners: drop CPU request 500m→100m on DM/AiStation.Linux/WorldBuilder
All 3 fleet nodes were at 99% CPU REQUEST allocation; the 6 new pods
from the previous commit (3 deployments × 2 replicas × 500m) couldn't
schedule. Idle runners actually use ~1m CPU per `kubectl top pods`;
the 500m request was significantly over-provisioned. Burst headroom
preserved by limits.cpu: 2000m unchanged.

Follow-up: similar request right-sizing pass across the rest of the
runner fleet is queued for a future morning-routine sweep — 25 cores
reserved for ~50m actual use is a large slack we can reclaim cluster-
wide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:00:23 -05:00
Andrew Stoltz
ae030a5f33 runners: add github-runner Deployments for DeviceManagement + AiStation.Linux + WorldBuilder
Morning-routine 2026-05-26 — these three repos had ZERO online Linux PR-CI
capacity, blocking the Sprint 37 Cx-1 Linux-CI-migration PRs (DM #20/#21/
#22, AiStation.Linux #13, WorldBuilder #3/#4). Chicken-and-egg: the
migration PRs need Linux runners that the migration creates.

Each Deployment uses the same canonical emptyDir-only pattern as the
fresh-2026-05-26 updater deployment that lives just above:
  - replicas: 2 (room for parallel PR-CI without head-of-line blocking)
  - per-pod emptyDir caches (no RWO PVC contention)
  - shared github-runner-token secret (existing ACCESS_TOKEN PAT has
    org-wide read access)
  - LABELS: self-hosted,linux,fc-build-linux
  - DOTNET_INSTALL_DIR pinned per ADR-170 family

For AiStation.Linux specifically: Linux job will now pick up; the
Windows job in #13 remains queued indefinitely until the Windows runner
host substrate lands per Sprint 36 v2 Cl-2 / ADR-174 — that's a separate
arc, not this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:55:31 -05:00
bc8c35896f tests: add bluejay-ws runner-exclusion lint + fix 3 stale runner-fleet assertions (#30)
BLUEJAY-WS must never be a fleet GHA runner (operator directive 2026-05-26). Build-side analog of Sprint 9 safe-account exclusion. Also fixes 3 stale runner-fleet assertions broken by initContainer addition + replica tuning.
2026-05-26 03:42:01 +00:00
Andrew Stoltz
2cc91b6df0 runners: bump tts-reader memory limit 4Gi -> 8Gi
The github-runner-tts-reader pod was being OOMKilled (exit 137)
mid-`dotnet test` on the TtsReader 1000+ test suite. PR #21 CI
(the Windows -> Linux runner migration) flapped twice with the
"self-hosted runner lost communication" annotation before the
K8s-side symptoms surfaced via kubectl describe pod.

Requests bumped 1Gi -> 2Gi, limits 4Gi -> 8Gi. Comment added
inline so future fleet runs don't trip the same wall.

Unblocks PR #21 + the 9 other open TtsReader PRs that all rebase
through it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:31:48 -05:00
0d2090fe81 runners: add github-runner-updater Deployment (#29)
Close runner-fleet gap for FlowerCore.Updater. Matches Sprint 32 long-tail pattern; registers entry in fleet-lint required-set.
2026-05-26 03:24:13 +00:00
Andrew Stoltz
bc3548e715 runners: add github-runner-pimanager Deployment
FlowerCore.PiManager build run 26417714843 sat queued 5h with zero
self-hosted runners registered to the repo. PiManager was missed in
the Sprint 32 long-tail sweep — every other FC repo got a dedicated
repo-scoped Deployment with its own ACCESS_TOKEN registration, but
PiManager fell through the cracks.

Adds a 2-replica ephemeral runner Deployment matching the Signage /
DMS / Print.Web pattern (per-pod emptyDir caches, no shared PVC,
labels `self-hosted,linux,fc-build-linux`, shared github-runner-token
PAT). Once ArgoCD syncs, the queued job will pick up automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:33:44 -05:00
74333cc26b selenium: right-size hub + chrome + edge memory limits (#28) 2026-05-26 01:12:15 +00:00
Andrew Stoltz
7310fb88c2 selenium: right-size hub + chrome + edge memory limits
Edge node has been OOMKilled 51 times in 5 days (~1 every 2.4h) on a
1Gi memory limit. Chrome runs maxSessions=2 on the same 1Gi cap and
was idling at 684Mi — first concurrent session pushing the node to
~900Mi+ would be the next OOM. Hub was running at 766Mi against a 1Gi
limit (75%); no recent restarts but no headroom either.

Firefox node has been running at 2Gi memory limit for 9 days with
zero restarts — that is the right size for a Selenium 4.27 browser
node under our session profile (screen recording sidecar + 1080p
rendering + page captures). Match it.

Changes:
- Hub:    limit 1Gi -> 1.5Gi, request 512Mi -> 1Gi
- Chrome: limit 1Gi -> 2Gi,   request 512Mi -> 1Gi
- Edge:   limit 1Gi -> 2Gi,   request 512Mi -> 1Gi

CPU left alone on all three — observed utilization is well under the
existing limits (hub 54m / 500m, chrome 185m / 1, edge 11m / 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 20:11:41 -05:00
148bc87b9a runners: bake step-ca root CA into image (v20260525-stepca) (#27) 2026-05-26 01:04:14 +00:00
Andrew Stoltz
2a1e842100 runners: bake step-ca root CA into image (v20260525-stepca)
Without the IAmWorkin step-ca root CA in the runner image's system
trust store, .NET HttpClient calls from CI tests against
`*.iamworkin.lan` (e.g. `https://selenium.iamworkin.lan/session`) fail
with `The remote certificate is invalid because of errors in the
certificate chain: PartialChain`. FlowerCore.Print.Web's
`WebScreenshotService` unit tests hit this on every build.

Drop the step-ca root PEM into `/usr/local/share/ca-certificates/`,
run `update-ca-certificates` once during apt install, and let OpenSSL +
.NET-on-Linux read the regenerated `/etc/ssl/certs/ca-certificates.crt`
automatically — no `SSL_CERT_FILE` env var, no per-Deployment volume
mount.

Image rebuilt + saved + imported on all 3 schedulable RKE2 nodes
(rke2-server, rke2-agent1, rke2-agent2) before this PR — verified with
`ctr images list -q | grep stepca` on each node.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:55:38 -05:00
bc28430d24 selenium: allow github-runner namespace ingress on 4444 (#26) 2026-05-26 00:44:23 +00:00
Andrew Stoltz
cc92272217 selenium: allow github-runner namespace ingress on 4444
Unblocks CI jobs running in github-runner pods (e.g. FlowerCore.Print.Web
`help-screenshots`) from reaching selenium-hub. Previously the session
POST was DNAT'd to the hub pod IP then dropped at the Calico ingress
hook, surfacing as a 60s timeout against
http://selenium-hub.selenium.svc.cluster.local:4444 while the Selenium
UI showed 0/4 sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:43:12 -05:00
d6f4468a9c selenium: migrate hub + 3 nodes into ArgoCD-managed manifests (#25) 2026-05-26 00:09:35 +00:00
Andrew Stoltz
2f796a2ebd selenium: migrate hub + 3 nodes + service + ingressroute into ArgoCD
Previously orphan kubectl-applied since the Selenium Grid was first set
up. The `infra-selenium` ArgoCD app existed but only managed
`network-policy.yaml` — the deployments themselves drifted whenever
anyone `kubectl set env`'d or `kubectl scale`'d.

This commit captures the live state (with the 2026-05-25 maxSessions
bump for chrome already baked in) as canonical git source. ArgoCD's
ServerSideApply syncPolicy + selfHeal will now keep the grid in lock
step with this file.

Resources captured:
  - Service selenium-hub (ClusterIP, internal traffic on 4444)
  - Service selenium-hub-external (LoadBalancer, MetalLB 10.0.56.208)
  - Deployment selenium-hub
  - Deployment selenium-node-chrome (replicas=1, SE_NODE_MAX_SESSIONS=2)
  - Deployment selenium-node-firefox (replicas=1, maxSessions=1)
  - Deployment selenium-node-edge (replicas=1, maxSessions=1)
  - IngressRoute selenium-hub (Traefik, selenium.iamworkin.lan)

No live behavior change — server-side dry-run confirms unchanged for
hub/firefox/ingressroute, "configured" for hub-external + 3 deploys
(default-field reordering only; SSA + field managers handle the diff).

Refs: Sprint 33 morning-routine 2026-05-25 follow-up Q-MR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 19:08:55 -05:00
1f1f6823db runners: right-size replica counts per 14d CI activity (#24) 2026-05-26 00:01:47 +00:00
Andrew Stoltz
b92f74b63a runners: right-size replica counts per 14d CI activity data
Drop 2 → 1 for 10 deploys based on trailing-14d run counts:
  - LlmBridge, Media, Knowledge, Intranet.Web, DNS  (0 runs each)
  - Presentations (6), Redis (3), Provisioning (3),
    MessageBoard (3), MenuBoard (3)

Bump 2 → 3 for Print.Web: 12 runs in trailing 5d, and the
help-screenshots AAT job holds a runner 30+ min, creating
head-of-line blocking for parallel PRs.

Net change: -9 replicas (≈ -9 GiB committed memory).
Aligns with Sprint 33 morning-routine capacity audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 18:55:47 -05:00
Andrew Stoltz
cb7f7dbc4d authentik: generous startup/liveness probes for first-boot migration
The server pod was getting killed by liveness probe at 60s while still
waiting on migration DB lock (worker pod also running migrations against
same DB). Add startupProbe with 10.5 min budget so liveness doesn't fire
until migrations finish.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:03:03 -05:00
Andrew Stoltz
03126d5584 authentik: add fsGroup:1000 to server + worker so non-root uid can write /media
PermissionError: [Errno 13] Permission denied: '/media/public' in tenant_files
migration because Authentik container runs as uid 1000 but Longhorn PVC mounts
root:root by default. fsGroup on Pod securityContext recursively chgrps the
PVC mount to gid 1000 + chmods g+rwx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:58:35 -05:00
Andrew Stoltz
495e884c41 authentik: initial deployment at id.iamworkin.lan
Stack:
  - PostgreSQL 16 StatefulSet (Longhorn RWO 5Gi)
  - Redis 7 Deployment (no persistence)
  - Authentik server + worker (ghcr.io/goauthentik/server:2024.12.3)
  - Shared media PVC (Longhorn RWO 2Gi) between server+worker
  - Certificate via step-ca-acme ClusterIssuer
  - Traefik IngressRoute at id.iamworkin.lan

Secrets sourced from 1Password item 'authentik-credentials' (IAmWorkin
vault, id y6i74ch22q5wvm7znquq4nhhcu) via OnePasswordItem CRD. Fields:
AUTHENTIK_SECRET_KEY, POSTGRES_PASSWORD, REDIS_PASSWORD,
BOOTSTRAP_ADMIN_PASSWORD, BOOTSTRAP_ADMIN_TOKEN, BOOTSTRAP_ADMIN_EMAIL.

DNS A record id.iamworkin.lan -> 10.0.56.200 added via
scripts/pfsense-add-id-host.py (FlowerCore.DNS service was 502'ing on
pfSense diag_command.php response parsing).

Closes the immediate gap from PiManager OIDC Cohort 3 wire-up: PiManager
(a87cd6f) configures id.iamworkin.lan as JWT authority but the backend
was never deployed. Pirelay specifically is on Mode:apikey until this
backend is bootstrapped and a pimanager service-account exists.

Post-deploy bootstrap (manual once pods Ready):
  1. Login at https://id.iamworkin.lan/if/admin/ as akadmin
     using BOOTSTRAP_ADMIN_PASSWORD from 1Password.
  2. Create OAuth2/OpenID Provider for pimanager (issuer
     https://id.iamworkin.lan/application/o/pimanager/, audience 'pimanager').
  3. Create Application binding the provider.
  4. Create service account user 'pimanager-service-account', generate
     long-lived token, store in 1Password as 'pimanager-service-account'.
  5. Re-enable jwt mode on pirelay + un-mask puppet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 15:50:10 -05:00
Andrew Stoltz
65aa1e6104 fix(monitoring): point probe-printweb at /health (Q-MR-90)
Root path requires API key auth — `/` returned 401 to the blackbox
probe, firing PrintWebDown despite `/health` reporting Healthy.
Pattern: feedback_k8s_probes_behind_auth_middleware.

Mirrors FlowerCore.Notes scripts/monitoring/prometheus.yml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:52:02 -05:00
Andrew Stoltz
7f2a3b76b4 feat(github-runner): bake Ruby 3.3 into Linux self-hosted runner image (Q-MR-81) 2026-05-20 11:45:43 -05:00
ea73f00461 fix(fc-devicemgmt): remove self-referential Application resource (Q-MR-79)
ApplicationSet already creates infra-fc-devicemgmt; removing the in-repo Application child clears the self-reference drift.
2026-05-20 16:20:01 +00:00
Andrew Stoltz
25ace30a03 fix(fc-devicemgmt): remove self-referential Application resource (Q-MR-79) 2026-05-20 11:18:25 -05:00
Andrew Stoltz
ca574c2280 brochure: delete apps/brochure/ — full prune per operator decision 2026-05-19
Removes the apps/brochure/ directory entirely from the bluejay-infra
ApplicationSet glob. ArgoCD will:

  1. See infra-brochure has no git source -> mark for delete
  2. Prune the brochure namespace + Deployment + Service + Certificate
     + Secret + IngressRoute (all generated from the now-gone
     apps/brochure/brochure.yaml)
  3. Remove the infra-brochure Application from argocd ns

Operator decision 2026-05-19 (follow-up to 09387f9 ARCHIVED banner
commit): "Yes, prune argo for brochure. Probably fully deleted there."

The brochure subdomain project was a planning-chain misinterpretation
of "make TtsReader + AI Station production-ready" — see
memory/project_brochure_split_misinterpretation_archived_2026_05_19.md
in FlowerCore.Notes for the full decision record.

Reusable artifacts that were the operator's archive concern stay alive
in their actual homes:

- FlowerCore.Intranet.Web PR #8 content-NuGet carve-out: still in
  Intranet's master, may transfer to TtsReader / AI Station prod work
- Sprint 32 Cl-5 substrate (public-twin design ideas): SUPERSEDED banner
  in-place in FlowerCore.Notes docs/standards/, history preserved
- magpie-doc-writer + wren-walkthrough skill output: unchanged in
  Intranet's flowercore-whats-new/walkthroughs/galleries directories

Companion Notes-side commit updates the "scaled to 0 + ARCHIVED banner"
language in mvp-readiness.html + fleet-roadmap-2026-05-19-sprint36-v2.md
+ memory record to reflect full deletion instead.

Wrong-codebase image localhost/fc-brochure-web:v20260524-sprint32 is
being removed from rke2-server / rke2-agent1 / rke2-agent2 in a
follow-up step (reclaims ~800MB per node).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:42:30 -05:00
Andrew Stoltz
09387f90e1 brochure: ARCHIVED 2026-05-19 — was a misinterpretation, do not re-enable
The brochure split project was a misinterpretation of an operator request
to make TtsReader + AI Station production-ready. Somewhere in the planning
chain it spun up into a separate "showcase brochure product" with its own
host, repo, NuGet, and Codex pack — none of which the operator actually
wanted. The project itself is pointless and a waste of credits.

Archive (not delete) per operator decision 2026-05-19, because some work
shipped under the misinterpretation may still have reusable value:

- FlowerCore.Intranet.Web PR #8 (merged) introduced FlowerCore.Brochure.Content
  content-NuGet carve-out — pattern may apply to TtsReader/AiStation production
  polish.
- Sprint 32 Cl-5 substrate has design ideas for public-twin vs operator-host
  separation that may transfer.
- magpie-doc-writer / wren-walkthrough skills still author useful Intranet
  content — those skills stay active.

These manifests stay at replicas: 0 for ArgoCD continuity. Cleanup options
(move out of apps/* glob, or delete entirely) are documented in README.md
for an operator-explicit future call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:34:28 -05:00
Andrew Stoltz
e641ceab48 monitoring(irc-notify): criticals also batch hourly — fix per-fire spam
The first batching pass (bacac06) left critical-severity alerts on the
immediate-print path. That's still per-event spam for any persistent
critical (e.g. PrintPaperRollCritical fires every 30s Grafana evaluation
cycle when paper is <5%). Caught immediately after deploy: CUPS queue grew
0 → 8 jobs in 8 minutes from a single firing PrintPaperRollCritical.

This commit aligns with the operator's verbatim ask ("one alert an hour"):

- Critical-severity alerts now go into the digest buffer, NOT the
  immediate-print path. The digest payload already shows severity tags
  per alertname, so the operator still sees "[critical] X" in the printout.
- The explicit `alert_channel=thermal_print_immediate` label still bypasses
  batching, but only on NEW fingerprint arrival — it triggers a flush of
  the CURRENT digest (with the new alert included), then clears. Repeat
  webhooks for the same fingerprint dedupe in the buffer until the next
  hourly tick OR until the alert resolves. No fingerprint can spam.
- `add_to_digest` now returns bool (True = buffer grew, False = dedup /
  resolution / disabled) so the immediate-label path can flush only on
  state transitions.

Net effect: max 1 thermal print per BATCH_INTERVAL_MIN per alert fingerprint,
regardless of severity. Rules that genuinely need same-second paper opt in
via `alert_channel=thermal_print_immediate` (currently zero rules use this).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:22:25 -05:00
Andrew Stoltz
c263426ea5 fc-devicemgmt: operator image fix + Web scaled to 0
OPERATOR (PodCrashLoopBackOff cleared):
- Bumped image to v20260519-sp34cl3-fix (built from astoltz/FlowerCore.DeviceManagement@d9a3685
  after Sprint 34 Cl-3 stranded branch was merged via PR #19 squash).
- The v20260512-cx5 image was the broken Sprint 8 scaffold: generic Host
  builder, no kubeops, no Kestrel on :8080, no AddController chain. Readiness
  probe dial-tcp 8080 failed every restart.
- The new image ships the AddController chain for all 4 reconcilers
  (DeviceCrd / DeviceGroupCrd / DevicePolicyCrd / RemoteCommandCrd) plus
  Kestrel on :8080 and /healthz.
- Image saved + scp'd + ctr-imported on rke2-server / rke2-agent1 / rke2-agent2
  before this commit. SHA256: 2cc79ee0a2313c550268d1244f805ae41b396362148dd5603061cc15b6f7fa7e

WEB (DeploymentReplicasMismatch cleared via scale-to-0):
- Web pod cannot start. Two upstream gaps must close first:
  1) MySQL DB instance + user `fc_devicemgmt` / database `flowercore_devicemgmt`
     are not provisioned in fc-mysql. Cluster has zero MySqlInstanceCrds and
     no `mysql.fc-mysql.svc:3306` Service.
  2) 1Password vault item `IAmWorkin/FlowerCore DeviceManagement Runtime` is
     missing (5 fields: DB-Password + 4 mTLS PEMs). OnePasswordItem CRD has
     been stuck Ready=False since 2026-05-18T02:58.
- Same pattern as the brochure-web scale-to-0 in 914fed0 — make the cluster
  clean and quiet, let operator restart deploy on a real schedule.

Re-enable path is fully documented in the deployment-web.yaml header comment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:11:09 -05:00
Andrew Stoltz
bacac067cf monitoring(irc-notify): hourly digest batching for thermal printer
The thermal printer drained overnight (2026-05-18/19) because the old
notify.py POSTed one print job per Grafana webhook fire. With 9
concurrently-firing alerts (zabbix-postgres + fc-devicemgmt + brochure
+ PrintPaperRollLow), every evaluation cycle stamped fresh CUPS jobs
onto the queue until the operator physically powered the printer off.

This refactor:

- Adds env-var config: THERMAL_PRINT_ENABLED (master kill switch),
  BATCH_INTERVAL_MIN (default 60), BATCH_MAX_PENDING (default 50).
- IRC delivery stays per-event (operator wants the live stream).
- Thermal routing now:
  * critical/disaster/page severity OR alert_channel=thermal_print_immediate
    -> print immediately
  * alert_channel=thermal_print -> enqueue into hourly digest
  * RESOLVED -> remove from digest buffer (no resolution-spam prints)
  * else -> IRC only, no thermal
- Background digest_loop thread flushes the buffer hourly (or sooner
  if buffer hits BATCH_MAX_PENDING). Digest payload is a single
  Print.Web /api/print/alert POST listing distinct alertnames + per-rule
  target counts.
- New POST /flush endpoint (manual operator force-flush; useful for
  testing without waiting an hour).
- GET / returns config + buffer depth + per-stat counters for observability.

Net effect: max 1 thermal print per BATCH_INTERVAL_MIN for batched
warnings, plus immediate prints for criticals. Closes the 2026-05-18/19
alert-storm incident.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:56:14 -05:00
914fed08d8 fix(brochure): scale brochure-web to 0 — wrong codebase shipped (Intranet.Web binary in fc-brochure-web image, CrashLoopBackOff 296 restarts on /data read-only). Re-enable after Sprint 34 Cx-3 rebuild per docs/ai-agents/codex-prompts/2026-05-18-fc-brochure-web-rebuild-pack.md 2026-05-19 14:45:01 +00:00
Andrew Stoltz
200aeab032 ttsreader: deploy study mode repair image 2026-05-18 16:33:08 -05:00
Andrew Stoltz
8182616d4c ttsreader: point render piper to edge1 demo endpoint 2026-05-18 16:06:37 -05:00
Andrew Stoltz
f0862ac03c ttsreader: deploy sprint36 demo audio image 2026-05-18 16:04:59 -05:00
Andrew Stoltz
46c392605e monitoring: mirror PuppetServiceFailed alert from Notes (Sprint 33 Cx-7 Phase B)
Mirrors the live `puppet` alert group from
FlowerCore.Notes/scripts/monitoring/alerts.yml into the K8s ConfigMap so a
future in-cluster Prometheus inherits the ruleset automatically.

Source of truth remains the Notes file (live Podman Prometheus on noc1).
See feedback_monitoring_k8s_target_vs_live_podman.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 11:11:07 -05:00
89b147bbdd docs(openvox): document quadlet durability smoke (#12) 2026-05-18 04:53:02 +00:00
d7238a5e3b feat(brochure): add public brochure GitOps app (#13) 2026-05-18 04:52:37 +00:00
fc444a02a1 feat(chat): add public twin ingress (#11) 2026-05-18 04:52:20 +00:00
83d4883d55 feat(worldbuilder): pin k8s demo to fake backend (#10) 2026-05-18 04:52:11 +00:00
f8fe3b2688 feat(github-runner): add final long-tail runners (#9) 2026-05-18 04:52:01 +00:00
f2ab892ebc feat(github-runner): add Marquee + TtsReader per-repo runners (#8) 2026-05-18 03:27:14 +00:00
fef68a9560 feat(fc-devicemgmt): add Kubernetes deployment manifests (#1)
Sprint 8 IMPL lane Cx-5: fc-devicemgmt K8s manifests (rebased onto main 2026-05-18; 13 files, +944).

Namespace + Web Deployment (replicas:2, MySQL backend) + Operator Deployment (replicas:1, KubeOps leader-elect) + Service + Certificate (step-ca-acme ClusterIssuer) + Traefik IngressRoute (devices.iamworkin.lan internal) + ServiceAccount + ClusterRole + ClusterRoleBinding + NetworkPolicy (CNI DNAT-aware backend ports) + OnePasswordItem (5-field consolidated) + ArgoCD Application bootstrap shape + lint coverage.

Follow-ups (not merge blockers):
- localhost/fc-devicemgmt-{web,operator}:v20260512-cx5 must be imported to all 3 RKE2 nodes; pods will ErrImageNeverPull until imported.
- 1Password vault item 'FlowerCore DeviceManagement Runtime' must be created with 5 fields before pods can start.
- DNS devices.iamworkin.lan -> 10.0.56.200 already present.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 02:56:23 +00:00
Andrew Stoltz
6fe77225ae fix(github-runner): dedupe DOTNET_INSTALL_DIR+NUGET_PACKAGES on base+sharedpos
PR #5 rebase concatenated PR #5 env additions onto PR #7 env additions on
the base + sharedpos Deployments, producing duplicate-key validation
errors in ArgoCD's structured merge. The DOTNET_INSTALL_DIR and
NUGET_PACKAGES values are identical between PR #5 and PR #7; keep the
PR #7 originals and retain only the unique new env vars from PR #5
(DOTNET_CLI_TELEMETRY_OPTOUT, DOTNET_NOLOGO, DOTNET_GENERATE_ASPNET_CERTIFICATE).

No behavioral change — same final env var set, no duplicates.
2026-05-17 21:53:05 -05:00
634b9c4169 feat(github-runner): harden Linux runner fleet (#5) 2026-05-18 02:51:02 +00:00
b8c7e59005 Tighten Pi signage HDMI settle coverage (#3) 2026-05-18 02:35:17 +00:00
65ac8d6f01 feat(github-runner): pod-env DOTNET_INSTALL_DIR + initContainer for non-root runner (#7) 2026-05-18 02:25:18 +00:00
35844e0dbd chore(github-runner): un-park github-runner-sharedpos (Shared.Pos non-root build fixed) (#6) 2026-05-18 02:20:00 +00:00
b1e307151e chore(github-runner): un-park github-runner-sharedpos (replicas 1) after Shared.Pos CI fix merged 2026-05-17 21:54:16 +00:00
12b07219c7 chore(github-runner): park github-runner-sharedpos (replicas 0) until Cx-1 non-root fix
Shared.Pos build fails on non-root runner (setup-dotnet /usr/share/dotnet denied); churning runner drove HighCPU on rke2-agent2. Re-enable in the Sprint 30+ Cx-1 Linux-runner-fleet lane (DOTNET_INSTALL_DIR on pod env).
2026-05-17 21:50:35 +00:00
9fd32c4415 feat(monitoring): MacMiniRunnerOffline alert (Sprint 28 reconcile) 2026-05-17 19:50:29 +00:00
ad670fb344 feat(github-runner): add Shared.Pos repo-scoped Linux runner (unstick stuck publish) 2026-05-17 19:50:23 +00:00
Codex
6f6ca50987 fix(github-runner): switch RUNNER_TOKEN -> ACCESS_TOKEN; set RUN_AS_ROOT=false
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:08:56 +00:00
Codex
c7be58c1f7 chore(github-runner): bump replicas 0 -> 1 (PAT provisioned)
Operator provisioned GitHub PAT (Runner Registration) 1P item. OnePasswordItem CRD already materialized the secret. Unblocks CI fleet-wide.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:04:03 +00:00
Codex
a1f5a393cd chore(github-runner): rename 1P item to GitHub PAT (Runner Registration)
Renames the OnePasswordItem.itemPath from "GitHub Runner Registration
Token" to "GitHub PAT (Runner Registration)" so the runner 1P entry
sits next to its siblings — GitHub PAT (Gitea Mirrors) and GitHub PAT
(NuGet Packages) — under a consistent "GitHub PAT (...)" naming pattern
and API_CREDENTIAL category.

Existing field "credential" remains the consumer (RUNNER_TOKEN env).
Comment block clarified to require Administration:read/write fine-grained
PAT scope on target repos.

Old 1P item renamed to "[DEPRECATED 2026-05-16] GitHub Runner
Registration" — kept as recovery backup; can be hard-deleted after the
first successful runner pod start against the new item path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:01:41 +00:00
Codex
710340d8be chore(github-runner): rename 1P item to GitHub PAT (Runner Registration)
Renames the OnePasswordItem.itemPath from "GitHub Runner Registration
Token" to "GitHub PAT (Runner Registration)" so the runner 1P entry
sits next to its siblings — GitHub PAT (Gitea Mirrors) and GitHub PAT
(NuGet Packages) — under a consistent "GitHub PAT (...)" naming pattern
and API_CREDENTIAL category.

Existing field "credential" remains the consumer (RUNNER_TOKEN env).
Comment block clarified to require Administration:read/write fine-grained
PAT scope on target repos.

Old 1P item renamed to "[DEPRECATED 2026-05-16] GitHub Runner
Registration" — kept as recovery backup; can be hard-deleted after the
first successful runner pod start against the new item path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 10:27:58 -05:00
Andrew Stoltz
7d2daaa4f8 chore(github-runner): replicas 1 → 0 until 1Password token provisioned
github-runner-token OnePasswordItem exists but the underlying 1Password
vault item hasn't been created yet, so the operator can't mint the K8s
Secret. Pod stuck in CreateContainerConfigError → DeploymentReplicasMismatch
alert fires.

Scaling to 0 keeps the manifest infrastructure intact but stops trying
to schedule until operator:
1. Creates "GitHub Runner Registration Token" item in IAmWorkin vault
2. Generates a token at github.com/astoltz/<repo>/settings/actions/runners/new
3. Updates the OnePasswordItem itemPath to point at it
4. Bumps replicas back to 1 via PR
2026-05-15 16:18:19 -05:00
Andrew Stoltz
e50e103ba0 fix(zabbix): bump web probe timeouts 5s→15s + add failureThreshold
zabbix-web nginx+PHP-FPM container serves / at ~3-5s baseline with
occasional 6-7s spikes (probe path renders full dashboard via PHP).
kube-probe was killing the container after 3 consecutive 5s-timeout
499s, producing CrashLoopBackOff alert noise even though the app
was serving real traffic fine.

15s timeout absorbs the natural variance; explicit failureThreshold=3
documents the policy (was implicit default).

Closes the firing PodCrashLoopBackOff (zabbix-web) + pending
HTTPServiceSlow/HTTPServiceDegraded alerts. zabbix.iamworkin.lan
remains slow at the application layer (separate work — PHP-FPM
warm-up + Zabbix server "host not found" agent lookup spam need
their own fixes) but the pod restart loop stops.
2026-05-15 15:59:04 -05:00
Codex
e8094eb0bd ci(github-runner): add Phase 2 ephemeral Linux runner K8s manifest
Namespace github-runner with myoung34/github-runner:latest Deployment,
5Gi Longhorn RWO NuGet cache PVC, zero-privilege ServiceAccount, and
OnePasswordItem CRD for the registration token. EPHEMERAL=true mode
re-registers after each job; Recreate strategy avoids RWO multi-attach.
Targets fc-build-linux label; single replica pinned to rke2-server node.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 12:46:25 -05:00
8d87d9172c Add Pi signage Phase 1 player artifacts
Squash merge Sprint 14 Pi signage player artifacts.
2026-05-14 01:46:09 +00:00
Codex
cfd9743afa Add Apple TV signage docs manifest 2026-05-13 20:32:48 -05:00
Andrew Stoltz
5029e209cd kubevirt-vms: boot ci1 from server template 2026-05-12 16:58:18 -05:00
Codex
f298339152 fix(guacamole): add --- separator between macmini-vnc-creds OnePasswordItem and guacamole-branding ConfigMap
Missing document separator caused YAML to merge the OnePasswordItem's
top-level `spec: itemPath:` block into the ConfigMap that follows.
Result: a ConfigMap with a `.spec` field whose K8s schema does not
declare one, triggering ArgoCD's structured-merge diff to fail since
2026-05-11T15:30:54Z:

  Failed to compare desired state to live state: failed to calculate
  diff: error calculating structured merge diff: error building typed
  value from config resource: .spec: field not declared in schema

App stayed Healthy (live K8s tolerated the extra field — ConfigMap
ignored it) but ArgoCD's diff calc was broken, leaving the app stuck at
sync=Unknown for all 21 resources. Adding the missing `---` separator
makes the OnePasswordItem and ConfigMap proper sibling YAML documents,
each with its own kind-correct schema.

Diagnosed during 2026-05-12 morning routine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:26:03 -05:00
286 changed files with 42767 additions and 16810 deletions

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
/.gitattributes text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.sh text eol=lf

View File

@@ -116,8 +116,19 @@ 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`.
## 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
- OpenVox noc1 durability runbook: `docs/runbooks/openvoxserver-quadlet-durability.md`
- Cert-manager recovery playbook: `FlowerCore.Notes/memory/project_cert_manager_recovery_2026_04_22.md`
- Why pfSense DNS is required: `FlowerCore.Notes/memory/feedback_pfsense_dns_required_for_acme.md`
- Public DNS operator host: `https://dns.iamworkin.lan`

View File

@@ -0,0 +1,18 @@
{
"apiVersion": "cert-manager.io/v1",
"kind": "Certificate",
"metadata": {
"name": "aistation-web-tls",
"namespace": "fc-aistation"
},
"spec": {
"dnsNames": [
"aistation.iamworkin.lan"
],
"issuerRef": {
"kind": "ClusterIssuer",
"name": "step-ca-acme"
},
"secretName": "aistation-web-tls"
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:5000",
"AiStation__Appliance__FallbackEdition": "gx10",
"FLOWERCORE_AISTATION_EDITION": "gx10",
"FlowerCore__AgentZero__Enabled": "false",
"FlowerCore__DataDirectory": "/data",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/aistation.db",
"FlowerCore__Database__Provider": "Sqlite",
"FlowerCore__Editions__ProfileDirectory": "/app/editions",
"FlowerCore__Ollama__BaseUrl": "http://10.0.57.201:11434",
"FlowerCore__Operator__Enabled": "true",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.AiStation"
},
"kind": "ConfigMap",
"metadata": {
"labels": {
"app.kubernetes.io/name": "aistation-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "aistation-web-config",
"namespace": "fc-aistation"
}
}

View File

@@ -0,0 +1,111 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "aistation-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "aistation-web",
"namespace": "fc-aistation"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "aistation-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-06-16T14:00:18-05:00",
"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:v20260616-ai6-gx10-websurface-a8a3e9d-2f9f89e",
"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",
"nodeSelector": {
"kubernetes.io/arch": "arm64"
},
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "aistation-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "aistation-web",
"namespace": "fc-aistation"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`aistation.iamworkin.lan`)",
"services": [
{
"name": "aistation-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "aistation-web-tls"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "aistation-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "aistation-web",
"namespace": "fc-aistation"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 5000
}
],
"selector": {
"app.kubernetes.io/name": "aistation-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,54 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:8080",
"ChatOptions__BehaviorRuleEngine__FallbackOllamaBaseUrl": "http://10.0.57.201:11434",
"ChatOptions__BehaviorRuleEngine__ModelName": "gemma3:4b",
"ChatOptions__BehaviorRuleEngine__OllamaBaseUrl": "http://10.0.57.201:11434",
"FlowerCore__AI__DefaultModelName": "gemma3:12b",
"FlowerCore__AI__Helpdesk__SentimentEscalation__Enabled": "true",
"FlowerCore__AI__IrcBridge__AllowActionExecution": "false",
"FlowerCore__AI__IrcBridge__DefaultProfileSlug": "it-helpdesk",
"FlowerCore__AI__IrcBridge__Enabled": "true",
"FlowerCore__AI__IrcBridge__MentionProfileSlug": "it-helpdesk",
"FlowerCore__AI__IrcBridge__MentionReactiveMode": "mentions-only",
"FlowerCore__AI__Memory__EmbeddingModel": "nomic-embed-text",
"FlowerCore__AI__Memory__EnableSharedIndexingBackfill": "true",
"FlowerCore__AI__Memory__SharedIndexingDatabasePath": "/data/chat-memory-index.db",
"FlowerCore__AI__Memory__UseOllamaEmbeddings": "true",
"FlowerCore__AI__Memory__UseSharedIndexingAdapter": "true",
"FlowerCore__AI__OllamaBaseUrl": "http://10.0.57.201:11434",
"FlowerCore__AI__Skills__Intranet__IntranetBaseUrl": "http://intranet-web.intranet.svc.cluster.local",
"FlowerCore__AI__Skills__Intranet__PublicBaseUrl": "https://intranet.iamworkin.lan",
"FlowerCore__AI__Skills__Intranet__SearchBaseUrl": "https://intranet.iamworkin.lan",
"FlowerCore__AI__Skills__Library__LibraryApiUrl": "http://library-web.fc-library.svc.cluster.local",
"FlowerCore__AI__Skills__Print__PrintMcpBaseUrl": "http://10.0.57.16:5200",
"FlowerCore__AI__Skills__Retail__RetailApiUrl": "http://retail-web.fc-retail.svc.cluster.local",
"FlowerCore__AI__Voice__OutputRoot": "/data/audio",
"FlowerCore__AI__Voice__Piper__Host": "10.0.57.17",
"FlowerCore__AI__Voice__Piper__Port": "10400",
"FlowerCore__AI__Voice__RetentionDays": "30",
"FlowerCore__Anthropic__BaseUrl": "https://api.anthropic.com",
"FlowerCore__Anthropic__CheapModel": "claude-haiku-4-5-20251001",
"FlowerCore__Anthropic__DeepModel": "claude-opus-4-7",
"FlowerCore__Anthropic__DefaultModel": "claude-sonnet-4-6",
"FlowerCore__Anthropic__Enabled": "false",
"FlowerCore__Auth__Enabled": "false",
"FlowerCore__Auth__Oidc__Audience": "chat",
"FlowerCore__Auth__Oidc__Authority": "https://id.iamworkin.lan/application/o/chat/",
"FlowerCore__Auth__Oidc__ClientId": "chat",
"FlowerCore__Auth__Oidc__Enabled": "true",
"FlowerCore__Budget__ResponseCacheEnabled": "true",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/chat.db",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.Chat"
},
"kind": "ConfigMap",
"metadata": {
"name": "chat-web-config",
"namespace": "fc-chat"
}
}

View File

@@ -0,0 +1,159 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "chat-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "chat-web",
"namespace": "fc-chat"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "chat-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/healthz",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "chat-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "FlowerCore__Auth__Oidc__Authority",
"valueFrom": {
"secretKeyRef": {
"key": "issuer_url",
"name": "chat-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientId",
"valueFrom": {
"secretKeyRef": {
"key": "client_id",
"name": "chat-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientSecret",
"valueFrom": {
"secretKeyRef": {
"key": "client_secret",
"name": "chat-oidc-client",
"optional": true
}
}
}
],
"envFrom": [
{
"configMapRef": {
"name": "chat-web-config"
}
},
{
"secretRef": {
"name": "chat-web-secret",
"optional": true
}
}
],
"image": "localhost/fc-chat-web:v20260616-chat-md-a812a81",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "chat-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "chat-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "chat-web-public",
"namespace": "fc-chat"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`chat.flowercore.io`) && (Path(`/`) || Path(`/chat`) || PathPrefix(`/_blazor`) || PathPrefix(`/_framework`) || PathPrefix(`/_content`) || PathPrefix(`/avatars`) || PathPrefix(`/css`) || PathPrefix(`/js`) || PathPrefix(`/favicon`) || PathPrefix(`/chathub`)) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))",
"priority": 100,
"services": [
{
"name": "chat-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "cf-origin-flowercore-io"
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "chat-web",
"namespace": "fc-chat"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`chat.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "chat-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "chat-web-tls"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "chat-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "chat-web",
"namespace": "fc-chat"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app.kubernetes.io/name": "chat-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,18 @@
{
"apiVersion": "cert-manager.io/v1",
"kind": "Certificate",
"metadata": {
"name": "remotedesktop-tls",
"namespace": "fc-desktop"
},
"spec": {
"dnsNames": [
"desktop.iamworkin.lan"
],
"issuerRef": {
"kind": "ClusterIssuer",
"name": "step-ca-acme"
},
"secretName": "remotedesktop-tls"
}
}

View File

@@ -0,0 +1,93 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "remotedesktop-operator",
"app.kubernetes.io/part-of": "flowercore-remotedesktop"
},
"name": "remotedesktop-operator",
"namespace": "fc-desktop"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "remotedesktop-operator"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "remotedesktop-operator",
"app.kubernetes.io/part-of": "flowercore-remotedesktop"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "RemoteDesktop__WebUrl",
"value": "http://remotedesktop-web.fc-desktop.svc.cluster.local.:8080"
}
],
"image": "localhost/fc-remotedesktop-operator:gx10-v1",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": "http",
"scheme": "HTTP"
},
"initialDelaySeconds": 15,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "operator",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": "http",
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 15,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"serviceAccount": "remotedesktop-operator",
"serviceAccountName": "remotedesktop-operator",
"terminationGracePeriodSeconds": 30
}
}
}
}

View File

@@ -0,0 +1,299 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "remotedesktop-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "remotedesktop-web",
"namespace": "fc-desktop"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "remotedesktop-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "remotedesktop-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "FlowerCore__Database__ConnectionStrings__Sqlite",
"value": "Data Source=/app/data/remotedesktop.db"
},
{
"name": "RemoteDesktop__Mode",
"value": "Remote"
},
{
"name": "RemoteDesktop__EnableApiDocs",
"value": "true"
},
{
"name": "RemoteDesktop__PoolWarmupEnabled",
"value": "false"
},
{
"name": "RemoteDesktop__PoolWarmupIntervalSeconds",
"value": "60"
},
{
"name": "RemoteDesktop__OrphanReconciler__Enabled",
"value": "true"
},
{
"name": "RemoteDesktop__OrphanReconciler__IntervalSeconds",
"value": "300"
},
{
"name": "RemoteDesktop__OrphanReconciler__GraceSeconds",
"value": "600"
},
{
"name": "RemoteDesktop__Audit__Enabled",
"value": "true"
},
{
"name": "RemoteDesktop__Audit__DualWrite",
"value": "true"
},
{
"name": "RemoteDesktop__UserVolumeClaimInitializer",
"value": "KubernetesExec"
},
{
"name": "RemoteDesktop__KubernetesNamespace",
"value": "fc-desktop"
},
{
"name": "RemoteDesktop__GuacamoleUrl",
"value": "http://guacamole.guacamole.svc.cluster.local.:8080/guacamole"
},
{
"name": "RemoteDesktop__GuacamolePublicUrl",
"value": "https://desktop.iamworkin.lan/guacamole"
},
{
"name": "RemoteDesktop__GuacamoleAdminUser",
"value": "guacadmin"
},
{
"name": "RemoteDesktop__GuacamoleJsonSecretKey",
"valueFrom": {
"secretKeyRef": {
"key": "password",
"name": "remotedesktop-guacamole-json-auth"
}
}
},
{
"name": "RemoteDesktop__TraefikClusterIp",
"value": "10.43.234.103"
},
{
"name": "RemoteDesktop__TraefikHostAliases__0",
"value": "chat.iamworkin.lan"
},
{
"name": "RemoteDesktop__TraefikHostAliases__1",
"value": "desktop.iamworkin.lan"
},
{
"name": "RemoteDesktop__TraefikHostAliases__2",
"value": "gitea.iamworkin.lan"
},
{
"name": "RemoteDesktop__TraefikHostAliases__3",
"value": "intranet.iamworkin.lan"
},
{
"name": "RemoteDesktop__TraefikHostAliases__4",
"value": "print.iamworkin.lan"
},
{
"name": "RemoteDesktop__TraefikHostAliases__5",
"value": "selenium.iamworkin.lan"
},
{
"name": "RemoteDesktop__NfsServer",
"value": "10.0.58.3"
},
{
"name": "RemoteDesktop__NfsBasePath",
"value": "/volume1/kubernetes/remotedesktop/users"
},
{
"name": "RemoteDesktop__SessionRecordingBasePath",
"value": "/var/lib/guacamole/recordings"
},
{
"name": "RemoteDesktop__MaxSessionsPerUser",
"value": "3"
},
{
"name": "FlowerCore__Auth__Oidc__Authority",
"valueFrom": {
"secretKeyRef": {
"key": "issuer_url",
"name": "remotedesktop-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientId",
"valueFrom": {
"secretKeyRef": {
"key": "client_id",
"name": "remotedesktop-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientSecret",
"valueFrom": {
"secretKeyRef": {
"key": "client_secret",
"name": "remotedesktop-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Enabled",
"value": "false"
},
{
"name": "FlowerCore__Auth__Oidc__Enabled",
"value": "false"
}
],
"envFrom": [
{
"secretRef": {
"name": "remotedesktop-secrets",
"optional": true
}
}
],
"image": "localhost/fc-remotedesktop-web:gx10-v1",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 15,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"startupProbe": {
"failureThreshold": 60,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/app/data",
"name": "data"
},
{
"mountPath": "/var/lib/guacamole/recordings",
"name": "recordings",
"readOnly": true,
"subPath": "guacamole/recordings"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"serviceAccount": "remotedesktop-web",
"serviceAccountName": "remotedesktop-web",
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "remotedesktop-data"
}
},
{
"name": "recordings",
"nfs": {
"path": "/volume1/kubernetes",
"server": "10.0.58.3"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "remotedesktop-web",
"namespace": "fc-desktop"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`desktop.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "remotedesktop-web",
"port": 8080
}
]
}
],
"tls": {
"secretName": "remotedesktop-tls"
}
}
}

View File

@@ -0,0 +1,24 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "remotedesktop-web",
"namespace": "fc-desktop"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 8080,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app.kubernetes.io/name": "remotedesktop-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,8 @@
{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"name": "remotedesktop-operator",
"namespace": "fc-desktop"
}
}

View File

@@ -0,0 +1,8 @@
{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"name": "remotedesktop-web",
"namespace": "fc-desktop"
}
}

View File

@@ -0,0 +1,183 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "fc-devicemgmt-operator",
"app.kubernetes.io/component": "operator",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-operator",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "fc-devicemgmt-operator",
"namespace": "fc-devicemgmt"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app": "fc-devicemgmt-operator"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"annotations": {
"flowercore.io/audit-trace-id": "runtime-activity-trace",
"prometheus.io/path": "/metrics",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app": "fc-devicemgmt-operator",
"app.kubernetes.io/component": "operator",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-operator",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT",
"value": "false"
},
{
"name": "POD_NAME",
"valueFrom": {
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.name"
}
}
},
{
"name": "POD_NAMESPACE",
"valueFrom": {
"fieldRef": {
"apiVersion": "v1",
"fieldPath": "metadata.namespace"
}
}
},
{
"name": "FLOWERCORE_KUBERNETES_OWNER_DEPLOYMENT",
"value": "fc-devicemgmt-operator"
},
{
"name": "FlowerCore__Service__Name",
"value": "FlowerCore.DeviceManagement.Operator"
},
{
"name": "FlowerCore__DeviceManagement__DefaultTenantId",
"value": "system"
}
],
"image": "localhost/fc-devicemgmt-operator:gx10-v1",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 20,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"name": "operator",
"ports": [
{
"containerPort": 8080,
"name": "metrics",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "50m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"serviceAccount": "fc-devicemgmt-operator",
"serviceAccountName": "fc-devicemgmt-operator",
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

@@ -0,0 +1,211 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "fc-devicemgmt-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "fc-devicemgmt-web",
"namespace": "fc-devicemgmt"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app": "fc-devicemgmt-web"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": 0,
"maxUnavailable": 1
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/healthz",
"flowercore.io/audit-trace-id": "runtime-activity-trace",
"kubectl.kubernetes.io/restartedAt": "2026-05-20T11:29:46-05:00",
"operator.1password.io/last-restarted": "2026-05-20T16:29:03Z",
"prometheus.io/path": "/metrics",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app": "fc-devicemgmt-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT",
"value": "false"
},
{
"name": "HOME",
"value": "/data"
},
{
"name": "FlowerCore__Service__Name",
"value": "FlowerCore.DeviceManagement.Web"
},
{
"name": "FlowerCore__DeviceManagement__DefaultTenantId",
"value": "system"
},
{
"name": "FlowerCore__Database__Provider",
"value": "Sqlite"
},
{
"name": "FlowerCore__Database__ConnectionStrings__Sqlite",
"value": "Data Source=/data/devicemgmt.db"
},
{
"name": "FlowerCore__Database__Password",
"valueFrom": {
"secretKeyRef": {
"key": "DB-Password",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__EventBus__Redis__Configuration",
"value": "redis.fc-redis.svc:6379"
}
],
"image": "localhost/fc-devicemgmt-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"name": "web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "1",
"memory": "768Mi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"startupProbe": {
"failureThreshold": 30,
"initialDelaySeconds": 5,
"periodSeconds": 5,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "fc-devicemgmt-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "devicemgmt",
"namespace": "fc-devicemgmt"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`devices.iamworkin.lan`)",
"services": [
{
"name": "fc-devicemgmt-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "fc-devicemgmt-web-tls"
}
}
}

View File

@@ -0,0 +1,33 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app": "fc-devicemgmt-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "fc-devicemgmt-web",
"namespace": "fc-devicemgmt"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app": "fc-devicemgmt-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,16 @@
{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"labels": {
"app.kubernetes.io/component": "operator",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-operator",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "fc-devicemgmt-operator",
"namespace": "fc-devicemgmt"
}
}

View File

@@ -0,0 +1,265 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "fc-distribution",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "fc-distribution",
"namespace": "fc-distribution"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "fc-distribution"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"flowercore.io/healthz-auth-policy": "allow-anonymous",
"prometheus.io/path": "/metrics",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "fc-distribution",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT",
"value": "false"
},
{
"name": "FlowerCore__Auth__Enabled",
"value": "true"
},
{
"name": "FlowerCore__Auth__Oidc__Enabled",
"value": "true"
},
{
"name": "FlowerCore__Auth__Oidc__Authority",
"value": "https://id.iamworkin.lan/application/o/distribution/"
},
{
"name": "FlowerCore__Auth__Oidc__Audience",
"value": "distribution"
},
{
"name": "FlowerCore__Auth__Oidc__ClientId",
"value": "distribution"
},
{
"name": "FlowerCore__Auth__Oidc__ClientSecret",
"valueFrom": {
"secretKeyRef": {
"key": "client_secret",
"name": "distribution-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Database__Provider",
"value": "Sqlite"
},
{
"name": "FlowerCore__Database__ConnectionStrings__Sqlite",
"value": "Data Source=/data/distribution.db"
},
{
"name": "FlowerCore__Distribution__Blobs__Root",
"value": "/blobs"
},
{
"name": "FlowerCore__Distribution__Signing__EditionCerts__kiosk-standard__CertPath",
"value": "/signing/kiosk-standard/chain.pem"
},
{
"name": "FlowerCore__Distribution__Signing__EditionCerts__kiosk-standard__KeyPath",
"value": "/signing/kiosk-standard/private-key.pem"
},
{
"name": "FlowerCore__Distribution__Signing__EditionCerts__aistation-field__CertPath",
"value": "/signing/aistation-field/chain.pem"
},
{
"name": "FlowerCore__Distribution__Signing__EditionCerts__aistation-field__KeyPath",
"value": "/signing/aistation-field/private-key.pem"
},
{
"name": "FlowerCore__Distribution__EntitlementPublic__PublicEditions__0",
"value": "*"
}
],
"image": "localhost/fc-distribution:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"name": "web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"startupProbe": {
"failureThreshold": 30,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 5,
"successThreshold": 1,
"timeoutSeconds": 1
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "sqlite",
"subPath": "distribution/data"
},
{
"mountPath": "/blobs",
"name": "blobs",
"subPath": "distribution/blobs"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
},
{
"mountPath": "/signing/kiosk-standard",
"name": "kiosk-standard",
"readOnly": true
},
{
"mountPath": "/signing/aistation-field",
"name": "aistation-field",
"readOnly": true
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsNonRoot": true
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "sqlite",
"nfs": {
"path": "/volume1/kubernetes",
"server": "10.0.58.3"
}
},
{
"name": "blobs",
"nfs": {
"path": "/volume1/kubernetes",
"server": "10.0.58.3"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
},
{
"name": "kiosk-standard",
"secret": {
"defaultMode": 256,
"secretName": "edition-kiosk-standard"
}
},
{
"name": "aistation-field",
"secret": {
"defaultMode": 256,
"secretName": "edition-aistation-field"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "fc-distribution-public",
"namespace": "fc-distribution"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`dist.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))",
"priority": 100,
"services": [
{
"name": "fc-distribution",
"port": 80
}
]
}
],
"tls": {
"secretName": "cf-origin-flowercore-io"
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "fc-distribution",
"namespace": "fc-distribution"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`dist.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "fc-distribution",
"port": 80
}
]
}
],
"tls": {
"secretName": "fc-distribution-tls-secret"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "fc-distribution",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "fc-distribution",
"namespace": "fc-distribution"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app.kubernetes.io/name": "fc-distribution"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:8080",
"Dms__AutoMessageDaemon__PollIntervalMinutes": "5",
"Dms__Weather__CacheFilePath": "/data/noaa-cache.json",
"Dms__Weather__State": "MN",
"FlowerCore__Auth__AcceptLegacyApiKey": "true",
"FlowerCore__Auth__AcceptLegacyJwt": "false",
"FlowerCore__Auth__Provider": "Oidc",
"FlowerCore__Auth__RoleClaimType": "fc:roles",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/dms.db",
"FlowerCore__Database__Provider": "Sqlite",
"FlowerCore__Tenant__DefaultTenantHosts__0": "dms.iamworkin.lan",
"FlowerCore__Tenant__JwtClaimsEnabled": "false",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.DMS",
"Security__AllowedOrigins__0": "https://dms.iamworkin.lan"
},
"kind": "ConfigMap",
"metadata": {
"name": "dms-web-config",
"namespace": "fc-dms"
}
}

View File

@@ -0,0 +1,169 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "dms-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "dms-web",
"namespace": "fc-dms"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "dms-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-06-12T16:05:19-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "dms-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "Kestrel__Endpoints__Http__Url",
"value": "http://+:8080"
},
{
"name": "Kestrel__Endpoints__Http__Protocols",
"value": "Http1"
},
{
"name": "Kestrel__Endpoints__Grpc__Url",
"value": "http://+:8081"
},
{
"name": "Kestrel__Endpoints__Grpc__Protocols",
"value": "Http2"
},
{
"name": "FlowerCore__Auth__OidcClientId",
"valueFrom": {
"secretKeyRef": {
"key": "client_id",
"name": "dms-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__OidcClientSecret",
"valueFrom": {
"secretKeyRef": {
"key": "client_secret",
"name": "dms-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__OidcAuthority",
"valueFrom": {
"secretKeyRef": {
"key": "issuer_url",
"name": "dms-oidc-client",
"optional": true
}
}
}
],
"envFrom": [
{
"configMapRef": {
"name": "dms-web-config"
}
},
{
"secretRef": {
"name": "dms-web-secrets"
}
}
],
"image": "localhost/fc-dms-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 180,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "dms-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
},
{
"containerPort": 8081,
"name": "grpc",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 18,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"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": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "dms-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "dms-web",
"namespace": "fc-dms"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`dms.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "dms-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "dms-web-tls"
}
}
}

View File

@@ -0,0 +1,34 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "dms-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "dms-web",
"namespace": "fc-dms"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
},
{
"name": "grpc",
"port": 8081,
"protocol": "TCP",
"targetPort": 8081
}
],
"selector": {
"app.kubernetes.io/name": "dms-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,542 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: fc-dns
labels:
app.kubernetes.io/part-of: flowercore
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dns-web-data
namespace: fc-dns
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: dns-web-config
namespace: fc-dns
data:
appsettings.Production.json: |
{
"FlowerCore": {
"Auth": {
"Enabled": true,
"Oidc": {
"Enabled": true,
"Audience": "dns",
"RequireHttpsMetadata": true
}
},
"Database": {
"Provider": "Sqlite",
"ConnectionStrings": {
"Sqlite": "Data Source=/data/dns.db"
}
},
"Tenant": {
"DefaultTenantId": "default",
"JwtClaimsEnabled": true,
"DefaultTenantHosts": [
"dns.iamworkin.lan"
]
},
"Audit": {
"HashChain": {
"BridgeSensitivity": {
"Distribution": "Warn"
}
}
},
"Dns": {
"RateLimits": {
"PermitLimit": 60,
"WindowSeconds": 60,
"QueueLimit": 0
}
}
}
}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dns-web
namespace: fc-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dns-web
rules:
- apiGroups:
- ""
resources:
- namespaces
- pods
- services
- secrets
- configmaps
verbs:
- get
- list
- watch
- apiGroups:
- cert-manager.io
resources:
- certificates
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dns-web
subjects:
- kind: ServiceAccount
name: dns-web
namespace: fc-dns
roleRef:
kind: ClusterRole
name: dns-web
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dns-web
namespace: fc-dns
labels:
app: dns-web
app.kubernetes.io/name: dns-web
app.kubernetes.io/part-of: flowercore
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: dns-web
template:
metadata:
labels:
app: dns-web
app.kubernetes.io/name: dns-web
app.kubernetes.io/part-of: flowercore
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "5320"
prometheus.io/path: "/metrics/prometheus"
flowercore.io/source-sha: "8b4c1fcdcf25b476fa6fad76309ba69e38c21e8b"
spec:
serviceAccountName: dns-web
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
fsGroup: 1654
fsGroupChangePolicy: OnRootMismatch
containers:
- name: dns-web
image: localhost/fc-dns-web:v20260617-gx10-f3-8b4c1fc
imagePullPolicy: Never
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
ports:
- containerPort: 5320
name: http
env:
- name: ASPNETCORE_URLS
value: http://+:5320
- name: ASPNETCORE_ENVIRONMENT
value: Production
- name: FlowerCore__Dns__Providers__PfSenseUnbound__FallbackPassword
valueFrom:
secretKeyRef:
name: pfsense-admin
key: password
- name: FlowerCore__Auth__Oidc__Authority
valueFrom:
secretKeyRef:
name: dns-oidc-client
key: issuer_url
optional: true
- name: FlowerCore__Auth__Oidc__ClientId
valueFrom:
secretKeyRef:
name: dns-oidc-client
key: client_id
optional: true
- name: FlowerCore__Auth__Oidc__ClientSecret
valueFrom:
secretKeyRef:
name: dns-oidc-client
key: client_secret
optional: true
- name: FlowerCore__Auth__ApiKey
valueFrom:
secretKeyRef:
name: dns-api-keys
key: api_key
optional: true
- name: FlowerCore__Mcp__ApiKey__Key
valueFrom:
secretKeyRef:
name: dns-api-keys
key: api_key
optional: true
- name: FlowerCore__Mcp__ServiceName
value: flowercore.dns
- name: FlowerCore__Auth__Enabled
value: "true"
- name: FlowerCore__Auth__Oidc__Enabled
value: "true"
- name: FlowerCore__Auth__Oidc__Audience
value: dns
volumeMounts:
- name: data
mountPath: /data
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
- name: config
mountPath: /app/appsettings.Production.json
subPath: appsettings.Production.json
readOnly: true
resources:
requests:
cpu: 50m
memory: 96Mi
limits:
cpu: 300m
memory: 384Mi
readinessProbe:
httpGet:
path: /healthz
port: 5320
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 5320
initialDelaySeconds: 20
periodSeconds: 30
volumes:
- name: data
persistentVolumeClaim:
claimName: dns-web-data
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}
- name: config
configMap:
name: dns-web-config
---
apiVersion: v1
kind: Service
metadata:
name: dns-web
namespace: fc-dns
spec:
selector:
app: dns-web
ports:
- port: 5320
targetPort: 5320
name: http
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: dns-web-ingress-isolation
namespace: fc-dns
spec:
podSelector:
matchLabels:
app: dns-web
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-dns
- ipBlock:
cidr: 10.42.0.0/16
- ipBlock:
cidr: 10.0.56.0/24
- ipBlock:
cidr: 10.0.57.0/24
- ipBlock:
cidr: 10.0.58.0/24
- ipBlock:
cidr: 10.0.68.0/27
ports:
- port: 5320
protocol: TCP
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: dns-web-cert
namespace: fc-dns
spec:
secretName: dns-web-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- dns.iamworkin.lan
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: dns-web
namespace: fc-dns
spec:
entryPoints:
- websecure
routes:
- match: Host(`dns.iamworkin.lan`)
kind: Rule
priority: 100
services:
- name: dns-web
port: 5320
tls:
secretName: dns-web-tls
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dns-acme-webhook
namespace: fc-dns
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dns-acme-webhook
namespace: fc-dns
labels:
app: dns-acme-webhook
app.kubernetes.io/name: dns-acme-webhook
app.kubernetes.io/part-of: flowercore
spec:
replicas: 1
selector:
matchLabels:
app: dns-acme-webhook
template:
metadata:
labels:
app: dns-acme-webhook
app.kubernetes.io/name: dns-acme-webhook
app.kubernetes.io/part-of: flowercore
annotations:
flowercore.io/source-sha: "8b4c1fcdcf25b476fa6fad76309ba69e38c21e8b"
spec:
serviceAccountName: dns-acme-webhook
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
fsGroup: 1654
fsGroupChangePolicy: OnRootMismatch
containers:
- name: dns-acme-webhook
image: localhost/fc-dns-acme-webhook:v20260617-gx10-f3-8b4c1fc
imagePullPolicy: Never
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
ports:
- containerPort: 9443
name: https
env:
- name: ASPNETCORE_URLS
value: https://+:9443
- name: ASPNETCORE_ENVIRONMENT
value: Production
- name: Kestrel__Certificates__Default__Path
value: /tls/tls.crt
- name: Kestrel__Certificates__Default__KeyPath
value: /tls/tls.key
- name: FlowerCore__Dns__AcmeWebhook__ServiceBaseUrl
value: http://dns-web:5320
- name: FlowerCore__Dns__AcmeWebhook__ApiKey
valueFrom:
secretKeyRef:
name: dns-api-keys
key: api_key
optional: true
- name: FlowerCore__Dns__AcmeWebhook__GroupName
value: acme.flowercore.io
- name: FlowerCore__Dns__AcmeWebhook__SolverName
value: flowercore-dns
- name: FlowerCore__Dns__AcmeWebhook__Version
value: v1alpha1
volumeMounts:
- name: tls
mountPath: /tls
readOnly: true
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
readinessProbe:
httpGet:
scheme: HTTPS
path: /readyz
port: https
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
httpGet:
scheme: HTTPS
path: /healthz
port: https
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 5
volumes:
- name: tls
secret:
secretName: dns-acme-webhook-tls
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: dns-acme-webhook
namespace: fc-dns
spec:
selector:
app: dns-acme-webhook
ports:
- port: 443
targetPort: https
name: https
type: ClusterIP
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: dns-acme-webhook-selfsigned
namespace: fc-dns
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: dns-acme-webhook-ca
namespace: fc-dns
spec:
secretName: dns-acme-webhook-ca
duration: 43800h
issuerRef:
name: dns-acme-webhook-selfsigned
commonName: ca.dns-acme-webhook.fc-dns
isCA: true
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: dns-acme-webhook-ca-issuer
namespace: fc-dns
spec:
ca:
secretName: dns-acme-webhook-ca
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: dns-acme-webhook-serving-cert
namespace: fc-dns
spec:
secretName: dns-acme-webhook-tls
duration: 8760h
issuerRef:
name: dns-acme-webhook-ca-issuer
dnsNames:
- dns-acme-webhook
- dns-acme-webhook.fc-dns
- dns-acme-webhook.fc-dns.svc
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1alpha1.acme.flowercore.io
annotations:
cert-manager.io/inject-ca-from: fc-dns/dns-acme-webhook-serving-cert
spec:
group: acme.flowercore.io
groupPriorityMinimum: 1000
service:
name: dns-acme-webhook
namespace: fc-dns
version: v1alpha1
versionPriority: 15
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dns-acme-webhook-solver
rules:
- apiGroups:
- acme.flowercore.io
resources:
- flowercore-dns
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dns-acme-webhook-solver
subjects:
- kind: ServiceAccount
name: cert-manager
namespace: cert-manager
roleRef:
kind: ClusterRole
name: dns-acme-webhook-solver
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,20 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:5000",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/library.db",
"FlowerCore__Database__Provider": "Sqlite",
"FlowerCore__Library__BaseUrl": "https://library.iamworkin.lan",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.Library",
"PrintService__BaseUrl": "http://print.iamworkin.lan:5200"
},
"kind": "ConfigMap",
"metadata": {
"name": "library-web-config",
"namespace": "fc-library"
}
}

View File

@@ -0,0 +1,110 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "library-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "library-web",
"namespace": "fc-library"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "library-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/health",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5000",
"prometheus.io/scrape": "true"
},
"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:gx10-v1",
"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"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "library-web",
"namespace": "fc-library"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`library.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "library-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "library-web-tls"
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "library-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "library-web",
"namespace": "fc-library"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 5000
}
],
"selector": {
"app.kubernetes.io/name": "library-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,292 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "fc-llm-bridge",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "fc-llm-bridge",
"namespace": "fc-llm-bridge"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "fc-llm-bridge"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/healthz",
"kubectl.kubernetes.io/restartedAt": "2026-06-14T15:12:25-05:00",
"prometheus.io/path": "/metrics",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "fc-llm-bridge",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT",
"value": "false"
},
{
"name": "FlowerCore__LlmBridge__SqliteConnectionString",
"value": "Data Source=/data/llm-bridge.db"
},
{
"name": "FlowerCore__LlmBridge__DefaultTenantId",
"value": "default"
},
{
"name": "FlowerCore__LlmBridge__DefaultAppName",
"value": "agent-zero"
},
{
"name": "FlowerCore__LlmBridge__UtilModel",
"value": "qwen2.5:1.5b"
},
{
"name": "FlowerCore__LlmBridge__EmbedModel",
"value": "nomic-embed-text"
},
{
"name": "FlowerCore__LlmBridge__ApiKeys__agent-zero-ws",
"valueFrom": {
"secretKeyRef": {
"key": "agent-zero-ws",
"name": "fc-llm-bridge-api-keys",
"optional": true
}
}
},
{
"name": "FlowerCore__LlmBridge__ApiKeys__agent-zero-k8s",
"valueFrom": {
"secretKeyRef": {
"key": "agent-zero-k8s",
"name": "fc-llm-bridge-api-keys",
"optional": true
}
}
},
{
"name": "FlowerCore__LlmBridge__ApiKeys__spare-1",
"valueFrom": {
"secretKeyRef": {
"key": "spare-1",
"name": "fc-llm-bridge-api-keys",
"optional": true
}
}
},
{
"name": "FlowerCore__LlmBridge__ApiKeys__spare-2",
"valueFrom": {
"secretKeyRef": {
"key": "spare-2",
"name": "fc-llm-bridge-api-keys",
"optional": true
}
}
},
{
"name": "FlowerCore__Chat__OllamaBaseUrl",
"value": "http://10.0.56.14:30976"
},
{
"name": "FlowerCore__Chat__HttpTimeout",
"value": "00:05:00"
},
{
"name": "FlowerCore__Chat__ModelRouter__DefaultRoutes__Balanced__Provider",
"value": "Ollama"
},
{
"name": "FlowerCore__Chat__ModelRouter__DefaultRoutes__Balanced__Model",
"value": "qwen2.5:14b"
},
{
"name": "FlowerCore__Chat__ModelRouter__DefaultRoutes__Cheap__Provider",
"value": "Ollama"
},
{
"name": "FlowerCore__Chat__ModelRouter__DefaultRoutes__Cheap__Model",
"value": "qwen2.5:7b"
},
{
"name": "FlowerCore__Chat__Anthropic__Enabled",
"value": "true"
},
{
"name": "FlowerCore__Chat__Anthropic__ApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "password",
"name": "anthropic-api-key"
}
}
},
{
"name": "FlowerCore__Chat__Anthropic__OrganizationId",
"valueFrom": {
"secretKeyRef": {
"key": "organization_id",
"name": "anthropic-api-key",
"optional": true
}
}
},
{
"name": "FlowerCore__Chat__Anthropic__BaseUrl",
"value": "https://api.anthropic.com"
},
{
"name": "FlowerCore__Chat__Anthropic__DefaultModel",
"value": "claude-sonnet-4-6"
},
{
"name": "FlowerCore__Chat__Anthropic__AnthropicVersion",
"value": "2023-06-01"
},
{
"name": "FlowerCore__Chat__Anthropic__Timeout",
"value": "00:05:00"
}
],
"image": "localhost/fc-llm-bridge:gx10-v2",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 15,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"name": "web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "1",
"memory": "768Mi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/data",
"name": "app-data"
}
]
}
],
"dnsConfig": {
"nameservers": [
"10.43.0.10"
],
"options": [
{
"name": "ndots",
"value": "2"
}
],
"searches": [
"fc-llm-bridge.svc.cluster.local",
"svc.cluster.local",
"cluster.local"
]
},
"dnsPolicy": "None",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "fc-llm-bridge-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "app-data"
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "fc-llm-bridge",
"namespace": "fc-llm-bridge"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`fc-llm-bridge.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "fc-llm-bridge",
"port": 8080
}
]
}
],
"tls": {
"secretName": "fc-llm-bridge-tls"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {},
"name": "fc-llm-bridge",
"namespace": "fc-llm-bridge"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 8080,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app.kubernetes.io/name": "fc-llm-bridge"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "v1",
"data": {
"appsettings.Production.json": "{\n \"DatabaseProvider\": \"Sqlite\",\n \"ConnectionStrings\": {\n \"Sqlite\": \"Data Source=/data/media.db\"\n },\n \"FlowerCore\": {\n \"Auth\": {\n \"Enabled\": true,\n \"Oidc\": {\n \"Authority\": \"https://id.iamworkin.lan/application/o/media/\",\n \"ClientId\": \"media\",\n \"ClientSecret\": \"\",\n \"Audience\": \"media\",\n \"RequireHttpsMetadata\": true\n }\n },\n \"Tenant\": {\n \"JwtClaimsEnabled\": false,\n \"DefaultTenantHosts\": [ \"media.iamworkin.lan\" ]\n }\n },\n \"Media\": {\n \"LibraryRoot\": \"/media/library\",\n \"Sources\": [\n {\n \"Name\": \"BlueJayNAS Video\",\n \"Driver\": \"Nfs\",\n \"MountedPath\": \"/media/library\",\n \"RemotePath\": \"nfs://10.0.58.3/volume1/video\",\n \"IsEnabled\": true,\n \"IsDefault\": true,\n \"Notes\": \"Synology NFS media share mounted read-only inside the cluster.\"\n }\n ],\n \"GeneratedRoot\": \"/data/generated\",\n \"TranscodeRoot\": \"/data/transcodes\",\n \"InboxPath\": \"/media/inbox\",\n \"InboxScanIntervalMinutes\": 5,\n \"ScanOnStartup\": false,\n \"ComputeChecksums\": false,\n \"FfmpegCommand\": \"ffmpeg\",\n \"FfprobeCommand\": \"ffprobe\",\n \"Hls\": {\n \"MaxConcurrentJobs\": 1\n },\n \"DefaultViewerName\": \"BlueJay\",\n \"Dlna\": {\n \"IsEnabled\": true,\n \"MulticastAddress\": \"239.255.255.250\",\n \"Port\": 1900,\n \"DiscoveryTimeoutSeconds\": 2,\n \"DescriptionFetchTimeoutSeconds\": 2,\n \"MaxResponsesPerSearchTarget\": 32,\n \"SearchTargets\": [\n \"urn:schemas-upnp-org:device:MediaRenderer:1\",\n \"urn:schemas-upnp-org:device:MediaServer:1\"\n ]\n }\n }\n}\n"
},
"kind": "ConfigMap",
"metadata": {
"name": "fc-media-config",
"namespace": "fc-media"
}
}

View File

@@ -0,0 +1,242 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "fc-media-web",
"app.kubernetes.io/name": "fc-media-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "fc-media-web",
"namespace": "fc-media"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "fc-media-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"flowercore.io/healthz-auth-policy": "allow-anonymous",
"kubectl.kubernetes.io/restartedAt": "2026-04-05T14:14:28-05:00",
"prometheus.io/path": "/metrics",
"prometheus.io/port": "5200",
"prometheus.io/scrape": "true"
},
"labels": {
"app": "fc-media-web",
"app.kubernetes.io/name": "fc-media-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "ASPNETCORE_URLS",
"value": "http://+:5200"
},
{
"name": "FlowerCore__Auth__Enabled",
"value": "true"
},
{
"name": "FlowerCore__Auth__Oidc__Enabled",
"value": "true"
},
{
"name": "FlowerCore__Auth__Oidc__Audience",
"value": "media"
},
{
"name": "FlowerCore__Auth__Oidc__ClientId",
"valueFrom": {
"secretKeyRef": {
"key": "client_id",
"name": "media-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientSecret",
"valueFrom": {
"secretKeyRef": {
"key": "client_secret",
"name": "media-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__Authority",
"valueFrom": {
"secretKeyRef": {
"key": "issuer_url",
"name": "media-oidc-client",
"optional": true
}
}
}
],
"image": "localhost/fc-media-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"httpHeaders": [
{
"name": "X-Forwarded-Proto",
"value": "https"
}
],
"path": "/healthz",
"port": 5200,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "fc-media-web",
"ports": [
{
"containerPort": 5200,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"httpHeaders": [
{
"name": "X-Forwarded-Proto",
"value": "https"
}
],
"path": "/healthz",
"port": 5200,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "4",
"memory": "4Gi"
},
"requests": {
"cpu": "500m",
"memory": "1Gi"
}
},
"startupProbe": {
"failureThreshold": 18,
"httpGet": {
"httpHeaders": [
{
"name": "X-Forwarded-Proto",
"value": "https"
}
],
"path": "/healthz",
"port": 5200,
"scheme": "HTTP"
},
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/app/appsettings.Production.json",
"name": "config",
"readOnly": true,
"subPath": "appsettings.Production.json"
},
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/data/transcodes",
"name": "transcodes"
},
{
"mountPath": "/media/library",
"name": "media-library",
"readOnly": true
},
{
"mountPath": "/media/inbox",
"name": "media-inbox"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"configMap": {
"defaultMode": 420,
"name": "fc-media-config"
},
"name": "config"
},
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "fc-media-data"
}
},
{
"name": "transcodes",
"nfs": {
"path": "/volume1/kubernetes/fc-media-transcodes",
"server": "10.0.58.3"
}
},
{
"name": "media-inbox",
"nfs": {
"path": "/volume1/kubernetes/fc-media-inbox",
"server": "10.0.58.3"
}
},
{
"name": "media-library",
"nfs": {
"path": "/volume1/video",
"readOnly": true,
"server": "10.0.58.3"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "fc-media-web",
"namespace": "fc-media"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`media.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "fc-media-web",
"port": 5200
}
]
}
],
"tls": {
"secretName": "fc-media-tls"
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app": "fc-media-web",
"app.kubernetes.io/name": "fc-media-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "fc-media-web",
"namespace": "fc-media"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 5200,
"protocol": "TCP",
"targetPort": 5200
}
],
"selector": {
"app": "fc-media-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,18 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:5000",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/menuboard.db",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.MenuBoard",
"Security__AllowedOrigins__0": "https://menuboard.iamworkin.lan"
},
"kind": "ConfigMap",
"metadata": {
"name": "menuboard-web-config",
"namespace": "fc-menuboard"
}
}

View File

@@ -0,0 +1,112 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "menuboard-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "menuboard-web",
"namespace": "fc-menuboard"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "menuboard-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": "menuboard-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "menuboard-web-config"
}
},
{
"secretRef": {
"name": "menuboard-web-secrets"
}
}
],
"image": "localhost/fc-menuboard-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "menuboard-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": "menuboard-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "menuboard-web",
"namespace": "fc-menuboard"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`menuboard.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "menuboard-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "menuboard-web-tls"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "menuboard-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "menuboard-web",
"namespace": "fc-menuboard"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 5000
}
],
"selector": {
"app.kubernetes.io/name": "menuboard-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,18 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:8080",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/messageboard.db",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.MessageBoard",
"Security__AllowedOrigins__0": "https://messageboard.iamworkin.lan"
},
"kind": "ConfigMap",
"metadata": {
"name": "messageboard-web-config",
"namespace": "fc-messageboard"
}
}

View File

@@ -0,0 +1,118 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "messageboard-web"
},
"name": "messageboard-web",
"namespace": "fc-messageboard"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "messageboard-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/health",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app": "messageboard-web"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "messageboard-web-config"
}
},
{
"secretRef": {
"name": "messageboard-web-secrets",
"optional": true
}
}
],
"image": "localhost/fc-messageboard-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 10,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 5
},
"name": "messageboard-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"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": "messageboard-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "messageboard-web",
"namespace": "fc-messageboard"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`messageboard.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "messageboard-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "messageboard-web-tls"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {},
"name": "messageboard-web",
"namespace": "fc-messageboard"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app": "messageboard-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "v1",
"data": {
"appsettings.Production.json": "{\n \"MySqlManager\": {\n \"CrdNamespace\": \"fc-tenant-default\",\n \"MySqlImage\": \"iwrk-nexus.iamworkin.lan:8444/iwrk-ubuntu-mysql:master\",\n \"PhpMyAdminImage\": \"phpmyadmin/phpmyadmin:latest\",\n \"PhpMyAdminDomain\": \"iamworkin.lan\",\n \"Advisor\": {\n \"DefaultPreset\": \"medium\",\n \"AutoDetectPreset\": true,\n \"MaxAutoPreset\": \"medium\",\n \"PresetOverride\": null\n }\n },\n \"ContainerBackend\": {\n \"Default\": \"Kubernetes\"\n },\n \"FlowerCore\": {\n \"Auth\": {\n \"Provider\": \"Oidc\",\n \"Enabled\": false,\n \"Oidc\": {\n \"Enabled\": true,\n \"Authority\": \"https://id.iamworkin.lan/application/o/mysql/\",\n \"Audience\": \"mysql\",\n \"ClientId\": \"mysql\",\n \"ClientSecret\": \"\"\n }\n },\n \"Tenant\": {\n \"JwtClaimsEnabled\": false,\n \"TenantClaimType\": \"fc:tenant\",\n \"ActorIdClaimType\": \"flowercore_actor_id\"\n },\n \"Database\": {\n \"Provider\": \"Sqlite\",\n \"ConnectionStrings\": {\n \"Sqlite\": \"Data Source=/data/mysql-manager.db\"\n }\n }\n }\n}\n"
},
"kind": "ConfigMap",
"metadata": {
"name": "mysql-web-config",
"namespace": "fc-mysql"
}
}

View File

@@ -0,0 +1,210 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "flowercore",
"app.kubernetes.io/name": "mysql-web"
},
"name": "mysql-web",
"namespace": "fc-mysql"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "mysql-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-04-17T19:52:14-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5300",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "mysql-web"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "FlowerCore__Auth__Enabled",
"value": "false"
},
{
"name": "FlowerCore__Auth__Oidc__Authority",
"valueFrom": {
"secretKeyRef": {
"key": "issuer_url",
"name": "mysql-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientId",
"valueFrom": {
"secretKeyRef": {
"key": "client_id",
"name": "mysql-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientSecret",
"valueFrom": {
"secretKeyRef": {
"key": "client_secret",
"name": "mysql-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__Audience",
"value": "mysql"
}
],
"image": "localhost/fc-mysql-web:v20260617-sec5-1148fd0",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/metrics/prometheus",
"port": 5300,
"scheme": "HTTP"
},
"initialDelaySeconds": 20,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "mysql-web",
"ports": [
{
"containerPort": 5300,
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/metrics/prometheus",
"port": 5300,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
},
{
"mountPath": "/app/appsettings.Production.json",
"name": "config",
"readOnly": true,
"subPath": "appsettings.Production.json"
}
]
}
],
"dnsConfig": {
"nameservers": [
"10.43.0.10"
],
"options": [
{
"name": "ndots",
"value": "2"
}
],
"searches": [
"fc-mysql.svc.cluster.local",
"svc.cluster.local",
"cluster.local"
]
},
"dnsPolicy": "None",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"serviceAccount": "mysql-web",
"serviceAccountName": "mysql-web",
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "mysql-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
},
{
"configMap": {
"defaultMode": 420,
"name": "mysql-web-config"
},
"name": "config"
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "mysql-web",
"namespace": "fc-mysql"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`mysql.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "mysql-web",
"port": 5300
}
]
}
],
"tls": {
"secretName": "mysql-web-tls"
}
}
}

View File

@@ -0,0 +1,23 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "mysql-web",
"namespace": "fc-mysql"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"port": 5300,
"protocol": "TCP",
"targetPort": 5300
}
],
"selector": {
"app.kubernetes.io/name": "mysql-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,8 @@
{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"name": "mysql-web",
"namespace": "fc-mysql"
}
}

View File

@@ -0,0 +1,18 @@
{
"apiVersion": "cert-manager.io/v1",
"kind": "Certificate",
"metadata": {
"name": "fc-network-web-tls",
"namespace": "fc-network"
},
"spec": {
"dnsNames": [
"network.iamworkin.lan"
],
"issuerRef": {
"kind": "ClusterIssuer",
"name": "step-ca-acme"
},
"secretName": "fc-network-web-tls"
}
}

View File

@@ -0,0 +1,210 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "fc-network-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-network-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "fc-network-web",
"namespace": "fc-network"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app": "fc-network-web"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": 0,
"maxUnavailable": 1
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/healthz",
"flowercore.io/audit-trace-id": "runtime-activity-trace",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5340",
"prometheus.io/scrape": "true"
},
"labels": {
"app": "fc-network-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-network-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:5340"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT",
"value": "false"
},
{
"name": "HOME",
"value": "/data"
},
{
"name": "FlowerCore__Auth__Enabled",
"value": "false"
},
{
"name": "FlowerCore__Database__Provider",
"value": "Sqlite"
},
{
"name": "FlowerCore__Database__ConnectionStrings__Sqlite",
"value": "Data Source=/data/network.db"
},
{
"name": "FlowerCore__Network__SnapshotStore__RootDirectory",
"value": "/data/snapshots"
},
{
"name": "FlowerCore__Network__SnapshotStore__UseGitHistory",
"value": "true"
},
{
"name": "FlowerCore__Network__IntendedModel__FilePath",
"value": "/data/intended.json"
}
],
"image": "localhost/fc-network-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5340,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "web",
"ports": [
{
"containerPort": 5340,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5340,
"scheme": "HTTP"
},
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "50m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"startupProbe": {
"failureThreshold": 30,
"httpGet": {
"path": "/healthz",
"port": 5340,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 5,
"successThreshold": 1,
"timeoutSeconds": 1
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "fc-network-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "fc-network-web",
"namespace": "fc-network"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`network.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "fc-network-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "fc-network-web-tls"
}
}
}

View File

@@ -0,0 +1,33 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app": "fc-network-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-network-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "fc-network-web",
"namespace": "fc-network"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 5340
}
],
"selector": {
"app": "fc-network-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,18 @@
{
"apiVersion": "cert-manager.io/v1",
"kind": "Certificate",
"metadata": {
"name": "php-web-tls",
"namespace": "fc-php"
},
"spec": {
"dnsNames": [
"php.iamworkin.lan"
],
"issuerRef": {
"kind": "ClusterIssuer",
"name": "step-ca-acme"
},
"secretName": "php-web-tls"
}
}

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "v1",
"data": {
"appsettings.Production.json": "{\n \"PhpManager\": {\n \"Namespace\": \"fc-php\",\n \"Slowlog\": {\n \"Path\": \"/var/log/apache2/php-fpm-slow.log\",\n \"Sidecar\": {\n \"Enabled\": true,\n \"Image\": \"\"\n }\n },\n \"PoolConfig\": {\n \"StartServers\": null,\n \"MinSpareServers\": null,\n \"MaxSpareServers\": null,\n \"ProcessIdleTimeoutSeconds\": 10,\n \"RequestTerminateTimeoutSeconds\": 30\n },\n \"Certificates\": {\n \"TlsInspector\": {\n \"LogGracefulDegradeWarnings\": false\n }\n },\n \"Backups\": {\n \"StoragePath\": \"/data/backups\"\n }\n },\n \"ApplicationArchives\": {\n \"WordPressCoreUrl\": \"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/wordpress/latest.tar.gz\",\n \"WordPressProxySourceUrl\": \"https://wordpress.org/latest.tar.gz\",\n \"WordPressLocalArchivePath\": \"/data/application-archives/latest.tar.gz\",\n \"MyBbCoreUrl\": \"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mybb/latest.zip\",\n \"MyBbProxySourceUrl\": \"https://mybb.com/download/\",\n \"MyBbLocalArchivePath\": \"/data/application-archives/mybb-latest.zip\",\n \"MediaWikiCoreUrl\": \"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mediawiki/latest.tar.gz\",\n \"MediaWikiProxySourceUrl\": \"https://releases.wikimedia.org/mediawiki/1.45/mediawiki-1.45.3.tar.gz\",\n \"MediaWikiLocalArchivePath\": \"/data/application-archives/mediawiki-latest.tar.gz\",\n \"DrupalCoreUrl\": \"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/drupal/latest.tar.gz\",\n \"DrupalProxySourceUrl\": \"https://ftp.drupal.org/files/projects/drupal-11.3.8.tar.gz\",\n \"DrupalLocalArchivePath\": \"/data/application-archives/drupal-latest.tar.gz\",\n \"BypassUpstreamTls\": true\n },\n \"ContainerBackend\": {\n \"Default\": \"Kubernetes\"\n },\n \"FlowerCore\": {\n \"Auth\": {\n \"Provider\": \"Oidc\",\n \"Enabled\": false,\n \"Oidc\": {\n \"Enabled\": true,\n \"Authority\": \"https://id.iamworkin.lan/application/o/php/\",\n \"Audience\": \"php\",\n \"ClientId\": \"php\",\n \"ClientSecret\": \"\"\n },\n \"Impersonation\": {\n \"Enabled\": false,\n \"DebugMode\": false\n }\n },\n \"Tenant\": {\n \"StrictMode\": false,\n \"JwtClaimsEnabled\": false,\n \"TenantClaimType\": \"fc:tenant\",\n \"ActorIdClaimType\": \"flowercore_actor_id\"\n },\n \"Account\": {\n \"AppId\": \"php\",\n \"DefaultTenantId\": \"default\",\n \"Impersonation\": {\n \"Enabled\": false,\n \"StrictMode\": false,\n \"TechSupportRoles\": [ \"tech-support\" ],\n \"Targets\": []\n }\n },\n \"Hosting\": {\n \"AutoDns\": {\n \"Enabled\": true,\n \"DnsManagerBaseUrl\": \"https://dns.iamworkin.lan/\",\n \"ZoneName\": \"iamworkin.lan\",\n \"RecordType\": \"A\",\n \"TargetAddress\": \"10.0.56.200\",\n \"Ttl\": 300,\n \"BypassTls\": true\n }\n },\n \"Database\": {\n \"Provider\": \"Sqlite\",\n \"ConnectionStrings\": {\n \"Sqlite\": \"Data Source=/data/php-manager.db\"\n }\n }\n }\n}\n"
},
"kind": "ConfigMap",
"metadata": {
"name": "php-web-config",
"namespace": "fc-php"
}
}

View File

@@ -0,0 +1,209 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "flowercore",
"app.kubernetes.io/name": "php-web"
},
"name": "php-web",
"namespace": "fc-php"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "php-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-06-13T01:59:27-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5400",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "php-web"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "FlowerCore__Auth__Enabled",
"value": "false"
},
{
"name": "FlowerCore__Auth__Oidc__Authority",
"valueFrom": {
"secretKeyRef": {
"key": "issuer_url",
"name": "php-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientId",
"valueFrom": {
"secretKeyRef": {
"key": "client_id",
"name": "php-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__ClientSecret",
"valueFrom": {
"secretKeyRef": {
"key": "client_secret",
"name": "php-oidc-client",
"optional": true
}
}
},
{
"name": "FlowerCore__Auth__Oidc__Audience",
"value": "php"
}
],
"image": "localhost/fc-php-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/metrics/prometheus",
"port": 5400,
"scheme": "HTTP"
},
"initialDelaySeconds": 20,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "php-web",
"ports": [
{
"containerPort": 5400,
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/metrics/prometheus",
"port": 5400,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
},
{
"mountPath": "/app/appsettings.Production.json",
"name": "config",
"readOnly": true,
"subPath": "appsettings.Production.json"
}
]
}
],
"dnsConfig": {
"nameservers": [
"10.43.0.10"
],
"options": [
{
"name": "ndots",
"value": "2"
}
],
"searches": [
"fc-php.svc.cluster.local",
"svc.cluster.local",
"cluster.local"
]
},
"dnsPolicy": "None",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"serviceAccount": "php-web",
"serviceAccountName": "php-web",
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "php-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
},
{
"configMap": {
"defaultMode": 420,
"name": "php-web-config"
},
"name": "config"
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "php-web",
"namespace": "fc-php"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`php.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "php-web",
"port": 5400
}
]
}
],
"tls": {
"secretName": "php-web-tls"
}
}
}

View File

@@ -0,0 +1,23 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "php-web",
"namespace": "fc-php"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"port": 5400,
"protocol": "TCP",
"targetPort": 5400
}
],
"selector": {
"app.kubernetes.io/name": "php-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,8 @@
{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"name": "php-web",
"namespace": "fc-php"
}
}

View File

@@ -0,0 +1,22 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:8080",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/presentations.db",
"FlowerCore__Database__Provider": "Sqlite",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.Presentations",
"PresentationStorage__HtmlBundlesRelativePath": "uploads/html-bundles",
"PresentationStorage__ImportsRelativePath": "uploads/imports",
"PresentationStorage__SlidesRelativePath": "uploads/slides",
"Security__AllowedOrigins__0": "https://presentations.iamworkin.lan"
},
"kind": "ConfigMap",
"metadata": {
"name": "presentations-web-config",
"namespace": "fc-presentations"
}
}

View File

@@ -0,0 +1,143 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "presentations-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "presentations-web",
"namespace": "fc-presentations"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "presentations-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-04-23T14:47:39-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "presentations-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "presentations-web-config"
}
},
{
"secretRef": {
"name": "presentations-web-secrets"
}
}
],
"image": "localhost/fc-presentations:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "presentations-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data",
"subPath": "data"
},
{
"mountPath": "/home/app/wwwroot/uploads",
"name": "data",
"subPath": "uploads"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"initContainers": [
{
"command": [
"/bin/sh",
"-lc",
"set -eu\nmkdir -p /mnt/pvc/data /mnt/pvc/uploads\n\nfor file in presentations.db presentations.db-shm presentations.db-wal; do\n if [ -f \"/mnt/pvc/${file}\" ] && [ ! -f \"/mnt/pvc/data/${file}\" ]; then\n mv \"/mnt/pvc/${file}\" \"/mnt/pvc/data/${file}\"\n fi\ndone\n\nif [ -d /mnt/pvc/dp-keys ] && [ ! -d /mnt/pvc/data/dp-keys ]; then\n mv /mnt/pvc/dp-keys /mnt/pvc/data/dp-keys\nfi\n\nfor directory in imports slides html-bundles; do\n if [ -d \"/mnt/pvc/${directory}\" ] && [ ! -d \"/mnt/pvc/uploads/${directory}\" ]; then\n mv \"/mnt/pvc/${directory}\" \"/mnt/pvc/uploads/${directory}\"\n fi\ndone\n\nmkdir -p \\\n /mnt/pvc/data/dp-keys \\\n /mnt/pvc/uploads/imports \\\n /mnt/pvc/uploads/slides \\\n /mnt/pvc/uploads/html-bundles\n"
],
"image": "localhost/fc-presentations:gx10-v1",
"imagePullPolicy": "Never",
"name": "storage-init",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/mnt/pvc",
"name": "data"
}
]
}
],
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "presentations-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "presentations-web",
"namespace": "fc-presentations"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`presentations.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "presentations-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "presentations-web-tls"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "presentations-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "presentations-web",
"namespace": "fc-presentations"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app.kubernetes.io/name": "presentations-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,20 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:5000",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/retail.db",
"FlowerCore__Database__Provider": "Sqlite",
"FlowerCore__Retail__BaseUrl": "https://retail.iamworkin.lan",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.Retail",
"PrintService__BaseUrl": "http://print.iamworkin.lan:5200"
},
"kind": "ConfigMap",
"metadata": {
"name": "retail-web-config",
"namespace": "fc-retail"
}
}

View File

@@ -0,0 +1,111 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "retail-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "retail-web",
"namespace": "fc-retail"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "retail-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/healthz",
"kubectl.kubernetes.io/restartedAt": "2026-06-02T01:34:08-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5000",
"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:gx10-v1",
"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"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "retail-web",
"namespace": "fc-retail"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`retail.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "retail-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "retail-web-tls"
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "retail-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "retail-web",
"namespace": "fc-retail"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 5000
}
],
"selector": {
"app.kubernetes.io/name": "retail-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,21 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:8080",
"Auth__ApiKey": "change-me",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/scoreboard.db",
"FlowerCore__Database__Provider": "Sqlite",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.Scoreboard",
"Security__AllowedOrigins__0": "https://scoreboard.iamworkin.lan",
"Security__ApiKey": "change-me"
},
"kind": "ConfigMap",
"metadata": {
"name": "scoreboard-web-config",
"namespace": "fc-scoreboard"
}
}

View File

@@ -0,0 +1,132 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "scoreboard-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "scoreboard-web",
"namespace": "fc-scoreboard"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "scoreboard-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-06-12T16:43:22-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "scoreboard-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "scoreboard-web-config"
}
}
],
"image": "localhost/fc-scoreboard-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 5
},
"name": "scoreboard-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 5
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"initContainers": [
{
"command": [
"/bin/sh",
"-c",
"chown -R 1654:1654 /data && chmod -R u+rwX,g+rwX /data"
],
"image": "localhost/fc-scoreboard-web:gx10-v1",
"imagePullPolicy": "Never",
"name": "scoreboard-data-permissions",
"resources": {},
"securityContext": {
"runAsGroup": 0,
"runAsUser": 0
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "scoreboard-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "scoreboard-web",
"namespace": "fc-scoreboard"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`scoreboard.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "scoreboard-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "scoreboard-web-tls"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "scoreboard-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "scoreboard-web",
"namespace": "fc-scoreboard"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app.kubernetes.io/name": "scoreboard-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,19 @@
{
"apiVersion": "v1",
"data": {
"ASPNETCORE_ENVIRONMENT": "Production",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
"ASPNETCORE_URLS": "http://+:8080",
"FlowerCore__Database__ConnectionStrings__Sqlite": "Data Source=/data/segmentdisplay.db",
"FlowerCore__Database__Provider": "Sqlite",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://otel-collector.monitoring.svc.cluster.local:4317",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_SERVICE_NAME": "FlowerCore.SegmentDisplay",
"Security__AllowedOrigins__0": "https://segmentdisplay.iamworkin.lan"
},
"kind": "ConfigMap",
"metadata": {
"name": "segmentdisplay-web-config",
"namespace": "fc-segmentdisplay"
}
}

View File

@@ -0,0 +1,113 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "segmentdisplay-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "segmentdisplay-web",
"namespace": "fc-segmentdisplay"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "segmentdisplay-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "segmentdisplay-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "segmentdisplay-web-config"
}
},
{
"secretRef": {
"name": "segmentdisplay-web-secrets",
"optional": true
}
}
],
"image": "localhost/fc-segmentdisplay-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "segmentdisplay-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 8080,
"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": "segmentdisplay-web-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "segmentdisplay-web",
"namespace": "fc-segmentdisplay"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`segmentdisplay.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "segmentdisplay-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "segmentdisplay-web-tls"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "segmentdisplay-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "segmentdisplay-web",
"namespace": "fc-segmentdisplay"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": 8080
}
],
"selector": {
"app.kubernetes.io/name": "segmentdisplay-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,151 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "signage-replay-web"
},
"name": "signage-replay-web",
"namespace": "fc-signage"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "signage-replay-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-05-04T13:50:05-05:00"
},
"labels": {
"app": "signage-replay-web"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:5280"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "ReplayFederation__Signage__Enabled",
"value": "true"
},
{
"name": "ReplayFederation__Signage__BaseUrl",
"value": "http://signage-web.fc-signage.svc.cluster.local.:5190"
},
{
"name": "ReplayFederation__Dms__Enabled",
"value": "true"
},
{
"name": "ReplayFederation__Dms__BaseUrl",
"value": "http://dms-web.fc-dms.svc.cluster.local.:8081"
}
],
"image": "localhost/fc-signage-replay-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5280,
"scheme": "HTTP"
},
"initialDelaySeconds": 20,
"periodSeconds": 20,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "signage-replay-web",
"ports": [
{
"containerPort": 5280,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5280,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "256Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/home/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

@@ -0,0 +1,230 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "signage-web"
},
"name": "signage-web",
"namespace": "fc-signage"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "signage-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-05-04T13:50:05-05:00"
},
"labels": {
"app": "signage-web"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:5190"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "TrafficSignal__RelayBridge__Enabled",
"value": "true"
},
{
"name": "TrafficSignal__RelayBridge__BaseUrl",
"value": "http://pirelay.iamworkin.lan:5100"
},
{
"name": "DatabaseProvider",
"value": "Sqlite"
},
{
"name": "ConnectionStrings__DefaultConnection",
"value": "Data Source=/data/signage.db"
},
{
"name": "Serilog__MinimumLevel__Default",
"value": "Information"
},
{
"name": "Serilog__WriteTo__0__Name",
"value": "Console"
},
{
"name": "Kestrel__Endpoints__Http__Url",
"value": "http://+:5190"
},
{
"name": "Kestrel__Endpoints__Grpc__Url",
"value": "http://+:5191"
},
{
"name": "FlowerCore__DefaultTenantId",
"value": "default"
},
{
"name": "FlowerCore__Signage__Announcements__AudibleEnabled",
"value": "true"
},
{
"name": "FlowerCore__Signage__Announcements__DefaultVoiceName",
"value": "en_US-amy-low"
},
{
"name": "FlowerCore__Signage__Announcements__Piper__Host",
"value": "10.0.57.17"
},
{
"name": "FlowerCore__Signage__Announcements__Piper__Port",
"value": "10400"
},
{
"name": "FlowerCore__Signage__Announcements__Piper__TimeoutSeconds",
"value": "120"
}
],
"image": "localhost/fc-signage-web:v20260617-gx10-f2-4da0983",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5190,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "signage-web",
"ports": [
{
"containerPort": 5190,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5190,
"scheme": "HTTP"
},
"initialDelaySeconds": 15,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "10m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"startupProbe": {
"failureThreshold": 30,
"periodSeconds": 5,
"successThreshold": 1,
"tcpSocket": {
"port": 5190
},
"timeoutSeconds": 1
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/app/data",
"name": "data"
},
{
"mountPath": "/app/Cache",
"name": "data"
},
{
"mountPath": "/app/storage",
"name": "data"
},
{
"mountPath": "/app/wwwroot/uploads",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
},
{
"mountPath": "/app/wwwroot/announcement-audio",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "signage-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "signage-replay-web",
"namespace": "fc-signage"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`replay.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "signage-replay-web",
"port": 5280
}
]
}
],
"tls": {
"secretName": "signage-replay-web-tls"
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "signage-web",
"namespace": "fc-signage"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`signage.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "signage-web",
"port": 5190
}
]
}
],
"tls": {
"secretName": "signage-web-tls"
}
}
}

View File

@@ -0,0 +1,24 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "signage-replay-web",
"namespace": "fc-signage"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 5280,
"protocol": "TCP",
"targetPort": 5280
}
],
"selector": {
"app": "signage-replay-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,24 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "signage-web",
"namespace": "fc-signage"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 5190,
"protocol": "TCP",
"targetPort": 5190
}
],
"selector": {
"app": "signage-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,133 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "signalcontrol-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "signalcontrol-web",
"namespace": "fc-signalcontrol"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "signalcontrol-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-04-22T23:55:51-05:00"
},
"labels": {
"app.kubernetes.io/name": "signalcontrol-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "ASPNETCORE_URLS",
"value": "http://+:5000"
},
{
"name": "ConnectionStrings__Default",
"value": "Data Source=/data/signalcontrol.db"
},
{
"name": "Logging__LogLevel__Default",
"value": "Information"
},
{
"name": "Auth__ApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "Auth__ApiKey",
"name": "signalcontrol-auth"
}
}
}
],
"image": "localhost/fc-signalcontrol-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": "http"
},
"timeoutSeconds": 5
},
"name": "signalcontrol-web",
"ports": [
{
"containerPort": 5000,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": "http"
},
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 4200,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "signalcontrol-data"
}
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "signalcontrol-web",
"namespace": "fc-signalcontrol"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`signalcontrol.iamworkin.lan`)",
"priority": 100,
"services": [
{
"name": "signalcontrol-web",
"port": 80
}
]
}
],
"tls": {
"secretName": "signalcontrol-web-tls"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "signalcontrol-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "signalcontrol-web",
"namespace": "fc-signalcontrol"
},
"spec": {
"internalTrafficPolicy": "Cluster",
"ports": [
{
"name": "http",
"port": 80,
"protocol": "TCP",
"targetPort": "http"
}
],
"selector": {
"app.kubernetes.io/name": "signalcontrol-web"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "v1",
"data": {
"index.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>FlowerCore</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: linear-gradient(135deg, #0a1628 0%, #1a2744 50%, #0d1f3c 100%);\n color: #e0e8f0;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n }\n .hero {\n text-align: center;\n padding: 3rem;\n max-width: 800px;\n }\n .logo {\n font-size: 5rem;\n margin-bottom: 1.5rem;\n filter: drop-shadow(0 0 20px rgba(74, 158, 255, 0.3));\n }\n h1 {\n font-size: 3rem;\n background: linear-gradient(135deg, #4a9eff, #7ab3ff);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n margin-bottom: 0.5rem;\n }\n .subtitle {\n font-size: 1.3rem;\n color: #7ab3ff;\n font-weight: 300;\n margin-bottom: 1rem;\n }\n .description {\n font-size: 1rem;\n color: #8aa8c4;\n line-height: 1.6;\n margin-bottom: 3rem;\n max-width: 600px;\n }\n .services {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;\n width: 100%;\n max-width: 700px;\n padding: 0 1rem;\n }\n .service {\n background: rgba(74, 158, 255, 0.08);\n border: 1px solid rgba(74, 158, 255, 0.2);\n border-radius: 8px;\n padding: 1.2rem;\n text-decoration: none;\n color: inherit;\n transition: all 0.2s;\n }\n .service:hover {\n background: rgba(74, 158, 255, 0.15);\n border-color: rgba(74, 158, 255, 0.5);\n transform: translateY(-2px);\n }\n .service h3 { color: #4a9eff; font-size: 0.95rem; margin-bottom: 0.3rem; }\n .service p { color: #8aa8c4; font-size: 0.8rem; }\n .status-bar {\n display: flex;\n gap: 2rem;\n margin-top: 2rem;\n padding: 1rem 2rem;\n background: rgba(74, 158, 255, 0.05);\n border-radius: 8px;\n border: 1px solid rgba(74, 158, 255, 0.1);\n }\n .status-item { text-align: center; }\n .status-item .value { color: #4a9eff; font-size: 1.5rem; font-weight: 700; }\n .status-item .label { color: #6a8ca4; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 1px; }\n .footer {\n margin-top: 3rem;\n color: #4a6580;\n font-size: 0.8rem;\n }\n .footer a { color: #4a6580; text-decoration: none; }\n .footer a:hover { color: #7ab3ff; }\n </style>\n</head>\n<body>\n <div class=\"hero\">\n <div class=\"logo\">&#x1F33B;</div>\n <h1>FlowerCore</h1>\n <p class=\"subtitle\">Blue Jay Lab</p>\n <p class=\"description\">\n Multi-tenant service management platform built on .NET 10,\n Kubernetes, and GitOps. Digital signage, telephony IVR,\n MySQL/PHP hosting, and infrastructure automation.\n </p>\n </div>\n <div class=\"services\">\n <a class=\"service\" href=\"https://gitea.flowercore.io\">\n <h3>Source</h3>\n <p>Gitea repositories</p>\n </a>\n <a class=\"service\" href=\"https://webmail.flowercore.io\">\n <h3>Mail</h3>\n <p>Webmail access</p>\n </a>\n <a class=\"service\" href=\"https://element.flowercore.io\">\n <h3>Chat</h3>\n <p>Matrix messaging</p>\n </a>\n <a class=\"service\" href=\"https://github.com/FlowerCoreIO\">\n <h3>GitHub</h3>\n <p>Open source</p>\n </a>\n </div>\n <div class=\"status-bar\">\n <div class=\"status-item\">\n <div class=\"value\">17</div>\n <div class=\"label\">Services</div>\n </div>\n <div class=\"status-item\">\n <div class=\"value\">13</div>\n <div class=\"label\">VLANs</div>\n </div>\n <div class=\"status-item\">\n <div class=\"value\">12k+</div>\n <div class=\"label\">Tests</div>\n </div>\n </div>\n <p class=\"footer\">\n FlowerCore &middot; Bare-metal RKE2 &middot; ArgoCD managed\n &middot; <a href=\"mailto:admin@flowercore.io\">Contact</a>\n </p>\n</body>\n</html>\n"
},
"kind": "ConfigMap",
"metadata": {
"name": "fc-landing-html",
"namespace": "fc-system"
}
}

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "v1",
"data": {
"default.conf": "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n\n location / {\n try_files $uri $uri/ =404;\n }\n\n location /healthz {\n access_log off;\n return 200 \"ok\";\n add_header Content-Type text/plain;\n }\n}\n"
},
"kind": "ConfigMap",
"metadata": {
"name": "fc-landing-nginx-conf",
"namespace": "fc-system"
}
}

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "v1",
"data": {
"appsettings.Production.json": "{\n \"FlowerCore\": {\n \"Database\": {\n \"Provider\": \"Sqlite\",\n \"ConnectionStrings\": {\n \"Sqlite\": \"Data Source=/app/data/kiosk.db\"\n }\n },\n \"Kiosk\": {\n \"Profiles\": {\n \"Backend\": \"K8s\",\n \"LonghornRoot\": \"/var/lib/flowercore/kiosk/profiles/data\"\n }\n },\n \"PrintWeb\": {\n \"Url\": \"http://10.0.57.16:5200\"\n }\n },\n \"Serilog\": {\n \"MinimumLevel\": {\n \"Default\": \"Information\",\n \"Override\": {\n \"Microsoft\": \"Warning\",\n \"Microsoft.EntityFrameworkCore\": \"Warning\"\n }\n },\n \"WriteTo\": [\n {\n \"Name\": \"Console\",\n \"Args\": {\n \"formatter\": \"Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact\"\n }\n }\n ],\n \"Enrich\": [\n \"FromLogContext\",\n \"WithProperty\"\n ],\n \"Properties\": {\n \"Service\": \"Kiosk.Web\",\n \"Environment\": \"Production\"\n }\n },\n \"LibraryWeb\": {\n \"Url\": \"http://library.iamworkin.lan:5100\"\n },\n \"Security\": {\n \"Mode\": \"apikey\",\n \"ApiKeys\": [],\n \"AdminApiKeys\": [],\n \"AgentApiKeys\": [],\n \"ExemptPaths\": [ \"/health\", \"/metrics\", \"/_blazor\", \"/_framework\", \"/css\", \"/hubs\" ]\n },\n \"Logging\": {\n \"LogLevel\": {\n \"Default\": \"Information\",\n \"Microsoft\": \"Warning\"\n }\n },\n \"AllowedHosts\": \"*\"\n}\n"
},
"kind": "ConfigMap",
"metadata": {
"name": "kiosk-web-config",
"namespace": "fc-system"
}
}

View File

@@ -0,0 +1,119 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "fc-landing"
},
"name": "fc-landing",
"namespace": "fc-system"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "fc-landing"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"labels": {
"app": "fc-landing"
}
},
"spec": {
"containers": [
{
"image": "nginx:alpine",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 80,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "nginx",
"ports": [
{
"containerPort": 80,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 80,
"scheme": "HTTP"
},
"initialDelaySeconds": 3,
"periodSeconds": 5,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "50m",
"memory": "64Mi"
},
"requests": {
"cpu": "5m",
"memory": "16Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/etc/nginx/conf.d/default.conf",
"name": "nginx-conf",
"subPath": "default.conf"
},
{
"mountPath": "/usr/share/nginx/html",
"name": "html"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"configMap": {
"defaultMode": 420,
"name": "fc-landing-nginx-conf"
},
"name": "nginx-conf"
},
{
"configMap": {
"defaultMode": 420,
"name": "fc-landing-html"
},
"name": "html"
}
]
}
}
}
}

View File

@@ -0,0 +1,275 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "kiosk-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/instance": "kiosk-web",
"app.kubernetes.io/managed-by": "flowercore-kiosk",
"app.kubernetes.io/name": "kiosk-web",
"component": "web",
"flowercore.io/created-by": "kiosk-fleshing-out-phase-2",
"flowercore.io/tenant-id": "default",
"project": "flowercore-kiosk"
},
"name": "kiosk-web",
"namespace": "fc-system"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "kiosk-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-05-06T21:21:19-05:00"
},
"labels": {
"app": "kiosk-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/instance": "kiosk-web",
"app.kubernetes.io/managed-by": "flowercore-kiosk",
"app.kubernetes.io/name": "kiosk-web",
"component": "web",
"flowercore.io/created-by": "kiosk-fleshing-out-phase-2",
"flowercore.io/tenant-id": "default",
"project": "flowercore-kiosk"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "Security__ApiKeys__0",
"valueFrom": {
"secretKeyRef": {
"key": "BrowserBypassApiKey",
"name": "kiosk-web-api-keys"
}
}
},
{
"name": "Security__AdminApiKeys__0",
"valueFrom": {
"secretKeyRef": {
"key": "AdminApiKey",
"name": "kiosk-web-api-keys"
}
}
},
{
"name": "Security__AgentApiKeys__0",
"valueFrom": {
"secretKeyRef": {
"key": "AgentApiKey",
"name": "kiosk-web-api-keys"
}
}
},
{
"name": "FlowerCore__Kiosk__Profiles__Backend",
"value": "K8s"
},
{
"name": "FlowerCore__Kiosk__Profiles__LonghornRoot",
"value": "/var/lib/flowercore/kiosk/profiles/data"
},
{
"name": "FlowerCore__PrintWeb__Url",
"value": "http://10.0.57.16:5200"
},
{
"name": "FlowerCore__PrintWeb__ApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "PrintWebApiKey",
"name": "kiosk-web-api-keys"
}
}
}
],
"image": "localhost/fc-kiosk-web:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 120,
"periodSeconds": 20,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "kiosk-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 45,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "1",
"memory": "512Mi"
},
"requests": {
"cpu": "250m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/app/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
},
{
"mountPath": "/var/lib/flowercore/kiosk/profiles",
"name": "user-profiles"
},
{
"mountPath": "/app/appsettings.Production.json",
"name": "appsettings-production",
"readOnly": true,
"subPath": "appsettings.Production.json"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"initContainers": [
{
"command": [
"sh",
"-c",
"mkdir -p /profiles/data && chown -R 1654:1654 /profiles/data && chmod -R u+rwX,g+rwX /profiles/data"
],
"image": "localhost/fc-kiosk-web:gx10-v1",
"imagePullPolicy": "Never",
"name": "fix-profile-perms",
"resources": {},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"add": [
"CHOWN",
"FOWNER"
],
"drop": [
"ALL"
]
},
"runAsGroup": 0,
"runAsNonRoot": false,
"runAsUser": 0
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/profiles",
"name": "user-profiles"
}
]
}
],
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "kiosk-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
},
{
"name": "user-profiles",
"persistentVolumeClaim": {
"claimName": "kiosk-user-profiles"
}
},
{
"configMap": {
"defaultMode": 420,
"items": [
{
"key": "appsettings.Production.json",
"path": "appsettings.Production.json"
}
],
"name": "kiosk-web-config"
},
"name": "appsettings-production"
}
]
}
}
}
}

View File

@@ -0,0 +1,147 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/component": "operator",
"app.kubernetes.io/instance": "mysql-operator",
"app.kubernetes.io/managed-by": "flowercore",
"app.kubernetes.io/name": "mysql-operator",
"app.kubernetes.io/part-of": "flowercore-mysql"
},
"name": "mysql-operator",
"namespace": "fc-system"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/instance": "mysql-operator",
"app.kubernetes.io/name": "mysql-operator"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-04-17T09:34:39-05:00"
},
"labels": {
"app.kubernetes.io/component": "operator",
"app.kubernetes.io/instance": "mysql-operator",
"app.kubernetes.io/managed-by": "flowercore",
"app.kubernetes.io/name": "mysql-operator",
"app.kubernetes.io/part-of": "flowercore-mysql"
}
},
"spec": {
"automountServiceAccountToken": true,
"containers": [
{
"env": [
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
}
],
"image": "localhost/fc-mysql-operator:v20260617-sec5-3c6649c",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 15,
"periodSeconds": 20,
"successThreshold": 1,
"timeoutSeconds": 3
},
"name": "mysql-operator",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 3
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "250m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/tmp",
"name": "tmp"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654,
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"serviceAccount": "mysql-operator",
"serviceAccountName": "mysql-operator",
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"emptyDir": {},
"name": "tmp"
}
]
}
}
}
}

View File

@@ -0,0 +1,164 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/component": "operator",
"app.kubernetes.io/instance": "php-operator",
"app.kubernetes.io/managed-by": "flowercore",
"app.kubernetes.io/name": "php-operator",
"app.kubernetes.io/part-of": "flowercore-php"
},
"name": "php-operator",
"namespace": "fc-system"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/instance": "php-operator",
"app.kubernetes.io/name": "php-operator",
"app.kubernetes.io/part-of": "flowercore-php"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": "25%",
"maxUnavailable": "25%"
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-04-17T10:09:10-05:00"
},
"labels": {
"app.kubernetes.io/component": "operator",
"app.kubernetes.io/instance": "php-operator",
"app.kubernetes.io/managed-by": "flowercore",
"app.kubernetes.io/name": "php-operator",
"app.kubernetes.io/part-of": "flowercore-php"
}
},
"spec": {
"automountServiceAccountToken": true,
"containers": [
{
"env": [
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
},
{
"name": "MySqlManager__BaseUrl",
"value": "https://mysql.iamworkin.lan/"
},
{
"name": "MySqlManager__BypassTls",
"value": "true"
},
{
"name": "PhpManager__BaseUrl",
"value": "https://php.iamworkin.lan/"
},
{
"name": "PhpManager__BypassTls",
"value": "true"
}
],
"image": "localhost/fc-php-operator:v20260617-sec5-0bfbf42",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 15,
"periodSeconds": 20,
"successThreshold": 1,
"timeoutSeconds": 3
},
"name": "php-operator",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 3
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "250m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/tmp",
"name": "tmp"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654,
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"serviceAccount": "php-operator",
"serviceAccountName": "php-operator",
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"emptyDir": {},
"name": "tmp"
}
]
}
}
}
}

View File

@@ -0,0 +1,29 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "fc-landing-public",
"namespace": "fc-system"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`flowercore.io`) || Host(`www.flowercore.io`)",
"priority": 100,
"services": [
{
"name": "fc-landing",
"port": 80
}
]
}
],
"tls": {
"secretName": "cf-origin-flowercore-io"
}
}
}

Some files were not shown because too many files have changed in this diff Show More