Compare commits

...

249 Commits

Author SHA1 Message Date
Andrew Stoltz
746bcb3dfd Pin RUM-3 web images 2026-06-21 14:59:26 -05:00
Andrew Stoltz
4211b41962 Set RUM-3 GX10 resource and health config 2026-06-21 14:55:16 -05:00
Andrew Stoltz
f27337a724 deploy(gx10): bump RUM circuit stability apps 2026-06-21 14:35:23 -05:00
Andrew Stoltz
309d40264b deploy(gx10): bump RUM clientlog apps 2026-06-21 14:07:23 -05:00
Andrew Stoltz
372ea9c1f7 deploy(gx10): bump IRC web services admin image 2026-06-21 06:38:31 -05:00
Andrew Stoltz
6eaca1c6f6 deploy(gx10): bump MCP Gateway chrome image 2026-06-21 04:59:02 -05:00
Andrew Stoltz
6475c2cd8d deploy(gx10): bump LlmBridge chrome standards image 2026-06-21 04:46:27 -05:00
Andrew Stoltz
989d4822af Deploy Network chrome visual probe 2026-06-21 04:23:04 -05:00
Codex Blue Jay
e4db08de35 deploy(gx10): roll DeviceManagement bootstrap trust profile 2026-06-21 04:07:44 -05:00
Andrew Stoltz
67cbbfb0db Deploy SegmentDisplay chrome visual probe 2026-06-21 04:07:15 -05:00
Andrew Stoltz
5e56dcb59c Harden GX10 Intranet pod boundary 2026-06-21 03:40:31 -05:00
Andrew Stoltz
e41c9f4ae7 Apply SEC-7 baseline to Knowledge 2026-06-21 03:22:49 -05:00
Andrew Stoltz
11122b5139 Apply SEC-7 baseline to WorldBuilder 2026-06-21 02:57:57 -05:00
Andrew Stoltz
cea1fcc609 Align WorldBuilder PVC storage class with live claim 2026-06-21 02:50:02 -05:00
Andrew Stoltz
84bdd0e23b Apply SEC-7 baseline to MCP gateway 2026-06-21 02:39:09 -05:00
Andrew Stoltz
9cca5b5651 Apply SEC-7 baseline to Apple MDM 2026-06-21 02:30:05 -05:00
Andrew Stoltz
f98603c9ff Deploy IRC Anope auth boundary image 2026-06-21 02:12:34 -05:00
Andrew Stoltz
d632275413 Add RemoteDesktop init container quota requests 2026-06-21 01:27:58 -05:00
Andrew Stoltz
638ea79b2c Bump RemoteDesktop web image for LSIO retirement 2026-06-21 01:25:24 -05:00
Codex GX10 Deploy
cb98d15497 deploy(gx10): roll DeviceManagement policy profile export 2026-06-21 01:25:01 -05:00
Andrew Stoltz
8f9c728ebd deploy(gx10): roll Updater SEC-5 image 2026-06-20 22:52:31 -05:00
Andrew Stoltz
14ed23f44f deploy(gx10): roll Signage SEC-5 image 2026-06-20 21:54:53 -05:00
Andrew Stoltz
668728ca17 deploy(gx10): roll DNS SEC-5 image 2026-06-20 20:55:18 -05:00
Andrew Stoltz
25b27b9782 deploy(gx10): roll DeviceManagement SEC-5 image 2026-06-20 20:35:14 -05:00
Andrew Stoltz
221c429ad8 Deploy RemoteDesktop SEC-5 image to GX10 2026-06-20 19:42:02 -05:00
Andrew Stoltz
f5fb47c450 Deploy IRC SEC-5 security scan image 2026-06-20 18:38:03 -05:00
Andrew Stoltz
c77ef12141 Fix RemoteDesktop GX10 data volume ownership 2026-06-20 17:49:05 -05:00
Andrew Stoltz
b3becd0a91 Deploy RemoteDesktop chrome standards image to GX10 2026-06-20 17:45:02 -05:00
Codex
61d67c3087 deploy(gx10): roll DeviceManagement catalog branding image 2026-06-20 16:22:05 -05:00
Andrew Stoltz
dfa7756b0f Deploy DeviceManagement Android catalog image to GX10 2026-06-20 14:44:01 -05:00
Andrew Stoltz
a0673070d3 Deploy IRC web CI fix image to GX10 2026-06-20 14:26:36 -05:00
Andrew Stoltz
7944863790 Deploy Signage web v20260620-signage-vr-7acb27d 2026-06-20 14:03:29 -05:00
Andrew Stoltz
523d6a119b Deploy DMS visual workflow image to GX10 2026-06-20 13:42:49 -05:00
Andrew Stoltz
ceb58f08f2 Deploy Scoreboard visual workflow image to GX10 2026-06-20 13:18:18 -05:00
Andrew Stoltz
1e7d817ea4 deploy(irc): fc-irc-web -> v20260620-irc-mgmt-0622993 (trivia schema-ensure fix + Blazor /manage console) 2026-06-20 13:08:23 -05:00
Andrew Stoltz
187fe53980 Deploy TtsReader visual workflow image to GX10 2026-06-20 13:02:49 -05:00
Andrew Stoltz
14a2b5b252 rollback(irc): fc-irc-web -> v20260620-irc-services-7a27fe3 (c227039 crash-loops: trivia tables missing on persisted DB; fixing forward) 2026-06-20 12:54:12 -05:00
Andrew Stoltz
5f6514a97a deploy(irc): fc-irc-web -> v20260620-irc-mgmt-c227039 (Blazor /manage console + trivia surface; auth-gated) 2026-06-20 12:50:02 -05:00
Andrew Stoltz
754d8a2b7d Deploy Telephony visual workflow image to GX10 2026-06-20 12:42:50 -05:00
Andrew Stoltz
63343a457a Deploy Chat visual workflow artifact 2026-06-20 11:30:38 -05:00
Andrew Stoltz
92f6a09fd8 Deploy MessageBoard chrome standards build to GX10 2026-06-20 10:27:57 -05:00
Andrew Stoltz
8591746a9f Deploy MenuBoard chrome standards build to GX10 2026-06-20 10:08:20 -05:00
Andrew Stoltz
26863d23d6 Deploy Media chrome standards probe 2026-06-20 09:20:51 -05:00
Andrew Stoltz
7580230484 Use default TLS store for GX10 WorldBuilder route 2026-06-20 08:56:52 -05:00
Andrew Stoltz
8e4a8d5588 Use empty TLS object for GX10 WorldBuilder route 2026-06-20 08:55:22 -05:00
Andrew Stoltz
360bdc065f Restore GX10 WorldBuilder TLS route secret reference 2026-06-20 08:52:48 -05:00
Andrew Stoltz
39ab1877c5 Fix GX10 WorldBuilder TLS route object 2026-06-20 08:49:34 -05:00
Andrew Stoltz
aa33201358 Preserve WorldBuilder GX10 app state resources 2026-06-20 08:41:39 -05:00
Andrew Stoltz
84dce3b0a8 Deploy GX10 WorldBuilder chrome standards image 2026-06-20 08:38:46 -05:00
Andrew Stoltz
8ee77aca44 Deploy GX10 Knowledge chrome standards image 2026-06-20 08:16:50 -05:00
Andrew Stoltz
a79627b72a Sync GX10 Traefik VIP and Intranet route 2026-06-20 07:57:23 -05:00
Andrew Stoltz
f81dcd3b36 Deploy GX10 Intranet chrome standards image 2026-06-20 07:41:43 -05:00
Andrew Stoltz
130c1c2243 Deploy GX10 Chat chrome standards image 2026-06-20 06:58:33 -05:00
Andrew Stoltz
68c147ad47 Deploy Chat chrome standards image 2026-06-20 06:56:35 -05:00
Andrew Stoltz
8472dc3100 Deploy IRC services proof image 2026-06-20 06:27:21 -05:00
Andrew Stoltz
f875ed304b Deploy IRC bot reactive proof image 2026-06-20 06:02:33 -05:00
Robot
e7a55c2f08 Deploy DeviceManagement check-in ingest to GX10 2026-06-20 02:23:15 -05:00
Andrew Stoltz
3860e2c4d6 Deploy IRC transport drop proof to GX10 2026-06-20 01:44:12 -05:00
Andrew Stoltz
6a22812d76 Deploy IRC R3 reply reaction proof to GX10 2026-06-20 01:17:29 -05:00
Andrew Stoltz
f1620f5025 Deploy IRC R3 send typing proof to GX10 2026-06-20 00:36:39 -05:00
Andrew Stoltz
b3cace3be1 Deploy IRC R3 chathistory batch filter to GX10 2026-06-20 00:18:50 -05:00
Andrew Stoltz
7c881e0e90 Deploy IRC R3 chathistory cleanup to GX10 2026-06-20 00:14:58 -05:00
Andrew Stoltz
e893b71615 Deploy IRC R3 chathistory client to GX10 2026-06-20 00:10:14 -05:00
Andrew Stoltz
81aa954387 Deploy IRC R3 live connector to GX10 2026-06-19 23:47:57 -05:00
Andrew Stoltz
76b7e44c35 Deploy IRC R3 parser catalog to GX10 2026-06-19 23:22:55 -05:00
Andrew Stoltz
5a7cdc9e57 Deploy IRC Chat sync import 2026-06-19 22:12:11 -05:00
Andrew Stoltz
e737ba2e24 Deploy IRC anonymous capability catalog 2026-06-19 21:57:44 -05:00
Andrew Stoltz
1b3bafae5b Deploy IRC protocol capability catalog 2026-06-19 21:53:18 -05:00
Andrew Stoltz
374993472f Deploy IRC Anope services status seam 2026-06-19 21:41:18 -05:00
Andrew Stoltz
18b8d8fd59 Allow loopback IRC RPC diagnostics 2026-06-19 21:14:58 -05:00
Andrew Stoltz
347fa58252 Roll IRC RPC TLS config revision 2026-06-19 21:12:19 -05:00
Andrew Stoltz
8cc06d0f90 Allow password-only IRC RPC TLS clients 2026-06-19 21:11:37 -05:00
Andrew Stoltz
19e8d6be71 Deploy IRC admin issuer fix image 2026-06-19 21:03:49 -05:00
Andrew Stoltz
56f73d68b9 Deploy IRC admin public auth route 2026-06-19 20:54:21 -05:00
Andrew Stoltz
4899ba9267 deploy(irc): enable readonly RPC log subscription 2026-06-19 19:48:10 -05:00
Andrew Stoltz
d0fe8bb3d1 deploy(irc): bump web for log subscription 2026-06-19 19:35:54 -05:00
Andrew Stoltz
737bbba433 fix(ttsreader): keep align model cache writable 2026-06-19 19:12:38 -05:00
Andrew Stoltz
c8c2cb81cf deploy(ttsreader): restore alignment sidecar 2026-06-19 19:09:32 -05:00
Andrew Stoltz
fcfa58d18e deploy(ttsreader): restore GX10 live TTS sidecars 2026-06-19 18:51:20 -05:00
Andrew Stoltz
77127d8ae0 deploy(irc): fc-irc-web -> v20260619-irc-r1rpc-d1616c4 (adds R1 mutating ops on top of read RPC; mutation gated server-side by flowercore-readonly rpc-class) 2026-06-19 18:48:21 -05:00
Robot
2627fb2cd0 deploy(devicemgmt): bump GX10 web to status readiness fix 2026-06-19 18:31:49 -05:00
Andrew Stoltz
251fc15143 deploy(irc): define FlowerCore readonly RPC class 2026-06-19 18:26:01 -05:00
Andrew Stoltz
ab41509f81 deploy(irc): inject hashed RPC password 2026-06-19 18:24:38 -05:00
Andrew Stoltz
416ff3e8a7 deploy(irc): use simple RPC username 2026-06-19 18:21:15 -05:00
Andrew Stoltz
fa49890b2b deploy(irc): allow internal RPC client range 2026-06-19 18:19:38 -05:00
Andrew Stoltz
6de33d5c20 deploy(irc): roll sanitized RPC credential 2026-06-19 18:17:03 -05:00
Andrew Stoltz
466bcaf720 deploy(irc): roll UnrealIRCd RPC config 2026-06-19 17:57:46 -05:00
Andrew Stoltz
6c11cbf21e deploy(irc): allow GX10 node RPC source 2026-06-19 17:56:44 -05:00
Andrew Stoltz
2cc0705e98 deploy(irc): roll web RPC TLS image 2026-06-19 17:55:33 -05:00
Andrew Stoltz
9ea463ba11 deploy(irc): enable readonly UnrealIRCd RPC on GX10 2026-06-19 17:42:34 -05:00
Andrew Stoltz
2dee33f21d deploy(chat): roll chat-web to v20260619-proofjay3b-bf2e147 (Proof Jay Phase 3b companion API)
Companion session adapter + /api/companion (SSE, fail-closed). Chat master@641eb3d.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:11:16 -05:00
Andrew Stoltz
132c76807a deploy(chat): roll chat-web to v20260619-proofjay-78b1f5f (Proof Jay companion packs)
proof-jay + proof-jay-helpdesk personalities (FlowerCore.Chat master@d48e668).
Image localhost/fc-chat-web:v20260619-proofjay-78b1f5f (arm64) imported to GX10 containerd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:55:22 -05:00
Andrew Stoltz
991c3f0f15 deploy(irc): roll web trust-store image 2026-06-19 12:51:03 -05:00
Robot
5f923c3f9f docs(gx10): update DeviceManagement agent mTLS posture 2026-06-19 12:07:00 -05:00
Robot
e65f9826e9 deploy(gx10): roll DeviceManagement patch ledger fix 2026-06-19 11:34:20 -05:00
Andrew Stoltz
2671073f52 deploy(irc): add gx10 management web app 2026-06-19 11:17:01 -05:00
Andrew Stoltz
d70d68afa0 deploy(drift-refresh): signage-replay gx10-v1 -> current master (4da0983)
signage-replay sidecar (was stale gx10-v1 while signage-web was current). Cross-RID arm64 publish (x86 protoc avoids the GB10 arm64 protoc SIGSEGV) + imported. Completes the drift sweep (9th refresh).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:16:41 -05:00
Andrew Stoltz
70ede651fb deploy(drift-refresh): aistation-web 37-behind -> current main (eb9d513)
aistation-web a8a3e9d (06-16, 37 commits behind) -> main 88ef7ab + Dockerfile.deploy (cross-RID). Built arm64 + imported (RKE2 socket).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 09:01:48 -05:00
Andrew Stoltz
bfa1d011c1 deploy(drift-refresh): scoreboard gx10-v1 -> current master (981d4b5)
scoreboard 981d4b5 (master 54546c9 + Dockerfile.deploy + NU1903 suppress). Built arm64 + imported (RKE2 socket).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 08:34:20 -05:00
Andrew Stoltz
f132d04e3f deploy(drift-refresh): library + retail gx10-v1 -> current master
library 0e027cc (main 6526113 + Dockerfile.deploy + NU1903 suppress), retail faae9db (main 29f6b0f + Dockerfile.deploy + NU1903 suppress). NFS-free, built arm64 + imported (RKE2 socket).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 08:30:14 -05:00
Andrew Stoltz
bf30cf0503 deploy(drift-refresh): presentations + llm-bridge gx10-v1/v2 -> current master
presentations a67ef22 (master d254737 + NU1903 suppress), llm-bridge 6ba5986 (master d354881 + NU1903 suppress + Shared.Chat 1.6.1 fix). Both NFS-free, built arm64 + imported (RKE2 socket).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 08:24:06 -05:00
Andrew Stoltz
5f3bf05258 deploy(drift-refresh): distribution + worldbuilder gx10-v1 -> current master
Drift sweep: refresh stale migration-era gx10-v1 baselines to current master (distribution 592ad75, worldbuilder edd6efc). Both built clean from arm64; imported to RKE2 socket. Other stale services (presentations/llm-bridge/library) blocked by SQLitePCLRaw 2.1.11 transitive vuln (NU1903) — pending dep bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 07:52:32 -05:00
Andrew Stoltz
5725c9b36b deploy(updater): enable Kiosk share link on GX10 2026-06-19 07:48:12 -05:00
Andrew Stoltz
b8fc8ba208 deploy(updater): add Kiosk public share link 2026-06-19 07:44:05 -05:00
Andrew Stoltz
5e7d9338b5 deploy(dms): current main (Phase 5->13) -> v20260619-dms-b203a71
Replaces stale generic gx10-v1 image (missing /openapi etc.) with current DMS main (b203a71). Auth gate-off default (no lockout). Image built+imported (RKE2 socket).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 07:31:58 -05:00
Robot
e543d4053a Verify DeviceManagement agent client certificates 2026-06-19 07:22:01 -05:00
Robot
5ce4f0d1e7 deploy(gx10): add DeviceManagement enrollment CA runtime 2026-06-19 06:45:09 -05:00
Andrew Stoltz
4c369cc7ec deploy(kiosk): bump GX10 web image for KI admin 2026-06-19 05:15:43 -05:00
Robot
299ce5aeed deploy(gx10): accept DER agent client cert headers 2026-06-19 01:58:12 -05:00
Robot
57a1afe159 deploy(gx10): bump DeviceManagement enrollment fix 2026-06-19 01:21:47 -05:00
Robot
0d71a789c2 deploy(gx10): add DeviceManagement agent mTLS route 2026-06-19 00:51:01 -05:00
Robot
14d89ba49d deploy(gx10): restore DeviceManagement agent heartbeat auth 2026-06-19 00:22:31 -05:00
Robot
0eda4362ce deploy(gx10): restore DeviceManagement agent cert auth 2026-06-19 00:05:00 -05:00
Andrew Stoltz
6f12ace02d deploy(knowledge): SEC-3 Search/Editions authorize + rebuild_index gate -> v20260619-sec3-6370c95
Removes [AllowAnonymous] bypass on Search/Editions + role-gates rebuild_index (PR #14, 6370c95). Image built+imported (RKE2 socket). Fail-open while auth off (inert until SEC-1); image now carries the hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:58:30 -05:00
Andrew Stoltz
0c03e53df9 deploy(chat): SEC-3 /api/memory + MCP write-tool auth -> v20260619-sec3-5a8859b
Closes the live anon /api/memory GET leak (PR #25, 5a8859b). Image built+imported (RKE2 socket). 0 anon consumers verified; UI reads via DI. Fail-closed 401, scheme reg'd unconditionally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:53:42 -05:00
Robot
62a3e75ddc deploy(gx10): roll DeviceManagement REST auth hardening 2026-06-18 23:53:18 -05:00
Andrew Stoltz
4bbd157c8f deploy(php): enable generated route WAF 2026-06-18 23:47:04 -05:00
Andrew Stoltz
1969285e4f deploy(gateway): SEC-3 /api/gateway auth -> v20260619-sec3-429e6cf
Closes the live anon /api/gateway/* REST bypass (PR #2, 429e6cf). Image built+imported to GX10 containerd. No consumers of the REST group; agent-zero uses /mcp (keyed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:44:25 -05:00
Andrew Stoltz
68a5f1ac5d deploy(php): allow manager DELETE through WAF 2026-06-18 20:37:47 -05:00
Andrew Stoltz
f0b122bac7 deploy(php): bump HM-4 Drupal ready image 2026-06-18 20:33:18 -05:00
Andrew Stoltz
c9538eeeef deploy(php): bump HM-4 probe fix image 2026-06-18 20:13:49 -05:00
Andrew Stoltz
c968e1c4d9 deploy(gx10): roll php web scoped templates 2026-06-18 19:11:14 -05:00
Robot
bc39da26a1 deploy(gx10): roll DeviceManagement auth challenge image 2026-06-18 19:09:22 -05:00
Robot
984e3423db deploy(gx10): roll DeviceManagement auth401 common image 2026-06-18 19:00:27 -05:00
Andrew Stoltz
5d0baa0fdd deploy(gx10): roll php web site-id recovery 2026-06-18 18:56:52 -05:00
Robot
f594d82c65 deploy(gx10): bump DeviceManagement auth status image 2026-06-18 18:43:06 -05:00
Andrew Stoltz
0b7d0fa476 deploy(gx10): roll php web tenant header fix 2026-06-18 18:30:25 -05:00
Andrew Stoltz
500b2484ab deploy(gx10): bump DeviceManagement web readiness image 2026-06-18 18:23:17 -05:00
Andrew Stoltz
c0a0341cef fix(gx10): route php operator to in-cluster manager 2026-06-18 18:16:42 -05:00
Robot
adafbb41f7 secure gx10 device management writes 2026-06-18 18:15:14 -05:00
Andrew Stoltz
09dce583bb deploy(gx10): roll mysql web tenant namespace fix 2026-06-18 18:05:12 -05:00
Andrew Stoltz
6d0464ec17 fix(gx10): add default tenant namespace 2026-06-18 17:40:38 -05:00
Andrew Stoltz
3b96a6272a deploy(gx10): restart php web for autodns config 2026-06-18 17:35:47 -05:00
Andrew Stoltz
061a0d61a8 fix(gx10): point php autodns at gx10 vip 2026-06-18 17:34:07 -05:00
Andrew Stoltz
ae6dfe9144 deploy: bump GX10 PHP and MySQL bypass proof images 2026-06-18 17:22:49 -05:00
Robot
28f9ac2ef9 platform: use current MetalLB VIP annotations 2026-06-18 16:44:49 -05:00
Andrew Stoltz
a7ba47e307 platform: dedicate GX10 Gitea SSH VIP 2026-06-18 16:40:50 -05:00
Andrew Stoltz
2e8cabcd63 platform: keep GX10 shared VIP traffic policy aligned 2026-06-18 16:30:24 -05:00
Andrew Stoltz
3948350ac2 platform: align GX10 Traefik source policy with live chart 2026-06-18 16:26:47 -05:00
Andrew Stoltz
ac153248c2 platform: preserve GX10 Traefik client source IP 2026-06-18 16:25:46 -05:00
Andrew Stoltz
9cef99739a security: add tenant allowlist and WAF canary proof 2026-06-18 16:21:08 -05:00
Robot
bd050c3d9b deploy(devicemgmt): roll command result hotfix 2026-06-18 15:02:50 -05:00
Robot
a41b22bca4 deploy(devicemgmt): roll APK artifact endpoint image 2026-06-18 14:39:48 -05:00
Andrew Stoltz
38590d3d5a deploy(knowledge): roll qwen3 canary profile image 2026-06-18 14:21:40 -05:00
Andrew Stoltz
27815cefca deploy(knowledge): roll catalog filter image 2026-06-18 14:12:04 -05:00
Andrew Stoltz
6e0d33b5b9 deploy(tenant): add bluejay.dev edge controls 2026-06-18 12:56:41 -05:00
Andrew Stoltz
b015c8a8e1 deploy(updater): roll feed signed manifest image 2026-06-18 12:42:42 -05:00
Andrew Stoltz
d51e55c78d deploy(updater): roll corrected GX10 containment image 2026-06-18 11:26:01 -05:00
Robot
f78e6747b4 deploy(apple-mdm): route scep to noc1 ca
Adds the GX10 /scep route to the noc1 Apple MDM SCEP CA without exposing NanoHUB APIs.
2026-06-18 11:23:00 -05:00
Andrew Stoltz
e543018bdc deploy(updater): recover GX10 image after packaging failure 2026-06-18 11:20:11 -05:00
Andrew Stoltz
d0c9717d90 deploy(updater): roll GX10 containment image 2026-06-18 11:08:12 -05:00
Andrew Stoltz
2c1aa3f0c8 deploy(updater): contain public UpdateCenter on GX10 2026-06-18 10:55:50 -05:00
Robot
aba9d7c995 deploy(gx10): pin DeviceManagement MDM-N8 image 2026-06-18 09:45:14 -05:00
Robot
a56e98422f deploy(gx10): wire Apple MDM runtime secret keys 2026-06-18 08:41:44 -05:00
Robot
27600b8b99 deploy(gx10): roll DeviceManagement InstallProfile payloads 2026-06-18 07:55:48 -05:00
Robot
9929a91812 deploy(gx10): roll DeviceManagement MDM policy payloads 2026-06-18 07:03:58 -05:00
Robot
5af4d9077a deploy(gx10): roll DeviceManagement readiness status 2026-06-18 05:55:52 -05:00
Robot
efa0434b9b deploy(gx10): roll DeviceManagement signed profiles 2026-06-18 05:38:01 -05:00
Robot
ad709e2317 deploy(gx10): configure DeviceManagement Apple MDM trust anchor 2026-06-18 04:58:06 -05:00
Robot
f636b5092c deploy(gx10): roll DeviceManagement MDM profile payloads 2026-06-18 04:56:05 -05:00
Robot
82d9d66f62 deploy(gx10): roll DeviceManagement app catalog endpoint 2026-06-18 01:14:54 -05:00
Robot
8b1f8df3dd deploy(gx10): add DeviceManagement enrollment profile endpoint 2026-06-18 00:22:03 -05:00
Andrew Stoltz
65af283aea deploy(updater): roll public exposure fix-forward image 2026-06-17 23:58:54 -05:00
Andrew Stoltz
b7d34da3d6 deploy(updater): gate public UpdateCenter host 2026-06-17 23:47:18 -05:00
Robot
63fde0a593 deploy(gx10): enable DeviceManagement NanoHUB bridge 2026-06-17 23:45:55 -05:00
Robot
764e4a8f49 deploy(gx10): use complete DeviceManagement NanoHUB image 2026-06-17 23:43:38 -05:00
Robot
3bdb9eee81 deploy(gx10): bump DeviceManagement web for NanoHUB bridge 2026-06-17 23:38:25 -05:00
Robot
83db2bbe6b deploy(gx10): add NanoHUB Apple MDM workload 2026-06-17 22:45:10 -05:00
Andrew Stoltz
c32327cdee deploy(devicemgmt): roll responsive enrollment page on GX10 2026-06-17 21:45:06 -05:00
Andrew Stoltz
818463562e deploy(devicemgmt): roll enrollment admin polish on GX10 2026-06-17 21:26:41 -05:00
Robot
f821c0e661 fc-devicemgmt: pin adopted GX10 prune hotpatch image 2026-06-17 20:51:58 -05:00
Robot
6c519bfd6a fc-devicemgmt: pin GX10 prune hotpatch image 2026-06-17 20:30:54 -05:00
Andrew Stoltz
ee14d3a2d0 whc4: front bluejay tenant route with CRS WAF 2026-06-17 19:54:26 -05:00
Andrew Stoltz
193b167d10 whc4: quiet PHP WAF health probes 2026-06-17 19:27:02 -05:00
Andrew Stoltz
ef782ed56d whc4: front PHP route with CRS WAF 2026-06-17 19:24:36 -05:00
Andrew Stoltz
41fb117ff0 whc4: deploy PHP tenant edge controls 2026-06-17 18:51:38 -05:00
Andrew Stoltz
ca1b1e8a3a sec4: roll MySQL and PHP storage measurement images 2026-06-17 17:57:16 -05:00
Andrew Stoltz
51572de3b2 sec4: roll MySQL and PHP runtime-limit images 2026-06-17 15:45:24 -05:00
Andrew Stoltz
a07aae9487 sec4: bump MySQL and PHP web rate-limit images 2026-06-17 14:40:32 -05:00
Codex
cc6399c4f3 Bump GX10 DeviceManagement web image 2026-06-17 14:08:07 -05:00
Andrew Stoltz
a0d79eeb8c hm4: own hosting operator CRDs and RBAC 2026-06-17 13:47:40 -05:00
Andrew Stoltz
4f7a5f3d20 fix(openbao): use arm64-resolving :2.5.5 tag (GX10 aarch64; amd64 digest won't pull)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:42:17 -05:00
Andrew Stoltz
4aad74a8aa feat(openbao): GX10 prod OpenBao StatefulSet (ADR-206 Phase-1)
Integrated-Raft single node, transit auto-unseal -> noc1 seal-bao
(10.0.56.10:8210, key gx10-unseal). Non-root (uid 100/gid 1000), internal
step-ca TLS listener. openbao-tls + openbao-seal secrets created out-of-band
(seal token + listener key never in git). local-path 2Gi Raft PVC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:41:02 -05:00
Andrew Stoltz
44608acae2 hm1: add GX10 MCP gateway wiring 2026-06-17 13:15:36 -05:00
Andrew Stoltz
54179a6c4c fix(chat): roll fc-chat to chatfix-54fd549 (arm64) — /tickets redirect + send-button recovery
Bump fc-chat image to localhost/fc-chat-web:v20260617-chatfix-54fd549, built
for arm64 on GX10 from Chat master 54fd549 + Common 0a4174d:
- OperatorRouteRedirect bounces ANY unauthorized path to sign-in (was: only
  operator/ops-chat prefixes, so /tickets sat on "Redirecting..." forever)
- FcAiChat send button stays clickable as Cancel while generating + closes
  the reconnect-recovery gap so it re-enables after a circuit drop

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:49:35 -05:00
Andrew Stoltz
dfaae3cbce deploy(segmentdisplay): roll non-root GX10 image 2026-06-17 10:52:19 -05:00
Andrew Stoltz
0020aa97ce Pin GX10 Network web SEC-5 image 2026-06-17 10:05:12 -05:00
Andrew Stoltz
b353058b47 gx10: deploy hardened MessageBoard web image 2026-06-17 09:41:56 -05:00
Andrew Stoltz
435e60a3f0 Deploy MenuBoard SEC-5 non-root image to GX10 2026-06-17 09:13:01 -05:00
Andrew Stoltz
ed32a65873 Deploy Media SEC-5 non-root image to GX10 2026-06-17 08:47:40 -05:00
Andrew Stoltz
18f4f657f8 Deploy Intranet SEC-5 non-root image to GX10
Pin localhost/fc-intranet-web:v20260617-sec5-intranet-1abdf90 and apply restricted pod/container security contexts plus writable /data, /tmp, and /app/logs mounts.
2026-06-17 08:18:31 -05:00
Andrew Stoltz
cf8cc4ba54 deploy(chat): roll non-root GX10 image 2026-06-17 07:46:28 -05:00
Andrew Stoltz
2cfd340833 deploy(dns): roll non-root GX10 images 2026-06-17 06:36:07 -05:00
Andrew Stoltz
983406b886 deploy(php): roll non-root GX10 web image 2026-06-17 05:59:36 -05:00
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
245 changed files with 35103 additions and 16272 deletions

View File

@@ -101,7 +101,7 @@ curl -sk -X DELETE https://dns.iamworkin.lan/api/v1/servers/<serverId>/zones/iam
- **StatefulSet PVC drift**: `volumeClaimTemplates` needs explicit `volumeMode: Filesystem` or ArgoCD SSA self-heals forever. See memory `feedback_argocd_statefulset_pvc_drift.md`.
- **IngressRoute namespace split**: this RKE2 Traefik install does not allow cross-namespace service refs. Keep the `IngressRoute`, backend `Service`, and TLS secret in the same namespace; if one host is shared across namespaces, duplicate the `Certificate` and move the route next to the destination service.
- **Public read-only hosts**: if a public host fronts a service that also exposes admin writes internally, add a Traefik route match like `Host(...) && (Method(GET) || Method(HEAD))` on the public edge instead of trusting the app to reject unsafe methods.
- **Public read-write allowlist hosts**: if a public host accepts a tightly bounded write surface (e.g. bootstrap-JWT POST), pin the allowlist as `(Method(GET) || Method(HEAD) || Method(POST) || Method(OPTIONS))`. PUT/PATCH/DELETE must still 404 at the route. Track A's `updatecenter.iamworkin.lan` / `updates.iamworkin.lan` are the canonical example. The lint test enforces this invariant.
- **Public read-write allowlist hosts**: if a public host accepts a tightly bounded write surface (e.g. bootstrap-JWT POST), pin the allowlist as `(Method(GET) || Method(HEAD) || Method(POST) || Method(OPTIONS))`. PUT/PATCH/DELETE must still 404 at the route. Internal UpdateCenter hosts (`updatecenter.iamworkin.lan` / `updates.iamworkin.lan`) are the canonical example. Public UpdateCenter delivery hosts (`update.flowercore.io` / `updates.flowercore.io`) stay GET/HEAD-only and share-link gated until an explicit operator decision changes that posture.
- **Traefik VIP netpols**: when a `NetworkPolicy` allows `10.0.56.200`, also allow the post-DNAT backend ports (`8443` for TLS plus `8080` or `8000` for HTTP) or Calico will drop the rewritten flow.
- **Auth-safe probes**: services behind API-key or global auth middleware should prefer `tcpSocket` probes unless `/health` is explicitly exempted before the middleware runs.
- **ArgoCD must use internal Gitea URL**: `http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git`, not the external HTTPS URL (step-ca cert isn't trusted by ArgoCD). The `ApplicationSet` and any hand-created `Application` must both use the internal URL.

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,120 @@
{
"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:v20260619-aistation-eb9d513",
"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": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"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,61 @@
# FlowerCore Apple MDM on GX10
This directory deploys the NanoHUB `v0.2.0` substrate for Apple MDM protocol
traffic at `https://mdm.iamworkin.lan`.
## Runtime
- Namespace: `fc-apple-mdm`
- Image: `localhost/fc-apple-mdm-nanohub:v0.2.0-20260617`
- Upstream digest: `ghcr.io/micromdm/nanohub:latest@sha256:e36a50db2dc3d2bf736645e58712f622c04b05b28487390981905ef4d0be5fbd`
- Persistent state: `fc-apple-mdm-data` on `local-path`, mounted at `/var/lib/nanohub`
- File backend DSN: `/var/lib/nanohub/db`
- Required secret: `Secret/fc-apple-mdm-runtime`, key `NANOHUB_API_KEY`
- Optional later bridge secret: `NANOHUB_WEBHOOK_URL`
- Required CA mount: `ConfigMap/fc-apple-mdm-root-ca`, key `root_ca.crt`
- SCEP backend: noc1 systemd service `step-ca-apple-mdm-scep`, forwarded through
selectorless `Service/fc-apple-mdm-scep` and `EndpointSlice/fc-apple-mdm-scep-noc1`
to `10.0.56.10:9080`
NanoHUB API authentication is HTTP Basic with username `nanohub` and password
from `NANOHUB_API_KEY`.
## Public Surface
The Traefik route intentionally exposes only:
- `/version`
- `/mdm`
- `/checkin`
- `/scep`
NanoHUB APIs under `/api/v1/*` stay cluster-internal for MDM-N1. The
DeviceManagement bridge can use the ClusterIP service directly once its NanoHUB
client lane lands.
SCEP is backed by the dedicated Apple-MDM-specific RSA step-ca hierarchy on
noc1, not by the IAmWorkin ACME CA. The live profile URL is:
```text
https://mdm.iamworkin.lan/scep/apple-mdm-scep
```
Do not point `APPLE_MDM_SCEP_URL` at a placeholder URL or at the ECDSA
IAmWorkin ACME CA; Smallstep SCEP requires an RSA intermediate/decrypter path.
## Deployment Notes
1. Create or refresh the runtime Kubernetes Secret from the 1Password item
`FlowerCore Apple MDM Runtime` before sync. GX10 does not yet depend on the
1Password operator for this workload.
2. Import `localhost/fc-apple-mdm-nanohub:v0.2.0-20260617` into GX10 containerd
before ArgoCD syncs. The deployment uses `imagePullPolicy: Never`.
3. Ensure `mdm.iamworkin.lan` resolves to the GX10 Traefik VIP `10.0.56.200`
before cert-manager requests `Certificate/fc-apple-mdm-tls`.
4. Prove `https://mdm.iamworkin.lan/version` after ArgoCD converges.
5. Prove SCEP CA publication with
`curl -sk -o /dev/null -w '%{http_code} %{size_download}\n' 'https://mdm.iamworkin.lan/scep/apple-mdm-scep?operation=GetCACert'`.
This lane does not create an APNs MDM push certificate, enrollment profile,
managed Wi-Fi payload, managed app install, or supervised iPad enrollment. Those
remain MDM-N2 through MDM-N8.

View File

@@ -0,0 +1,378 @@
# FlowerCore Apple MDM NanoHUB workload for the GX10 cluster.
# Secret values are copied into Kubernetes Secrets out of band until the
# 1Password operator exists on GX10; never commit secret data here.
---
apiVersion: v1
kind: Namespace
metadata:
name: fc-apple-mdm
labels:
app.kubernetes.io/part-of: flowercore
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fc-apple-mdm-root-ca
namespace: fc-apple-mdm
data:
root_ca.crt: |
-----BEGIN CERTIFICATE-----
MIIBxDCCAWqgAwIBAgIRAPY357G6ow6zMAL5+4bS2kkwCgYIKoZIzj0EAwIwQDEa
MBgGA1UEChMRSUFtV29ya2luIEFDTUUgQ0ExIjAgBgNVBAMTGUlBbVdvcmtpbiBB
Q01FIENBIFJvb3QgQ0EwHhcNMjYwMzA4MTgwNzExWhcNMzYwMzA1MTgwNzExWjBA
MRowGAYDVQQKExFJQW1Xb3JraW4gQUNNRSBDQTEiMCAGA1UEAxMZSUFtV29ya2lu
IEFDTUUgQ0EgUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ2n04X1
JZo5Zdq/i1Idv8+fqwZyAzBh7whbqj0SWsJL8UWRabCMqYCs7+dXO0xRSzqkwFDL
x+vooOai8RgRNhajRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/
AgEBMB0GA1UdDgQWBBRnuPPQR6iM/H6vOluiU3Sygayz8jAKBggqhkjOPQQDAgNI
ADBFAiEArQK9dYPGmAZsdYnjziuFVVE5NKZUcceYvGfGC+tLXUsCIAudF2zJrCRq
3mK50ZZET/fwTkJwiEF4824mjP8p1CKM
-----END CERTIFICATE-----
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fc-apple-mdm-data
namespace: fc-apple-mdm
labels:
app: fc-apple-mdm
app.kubernetes.io/name: fc-apple-mdm
app.kubernetes.io/part-of: flowercore
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fc-apple-mdm
namespace: fc-apple-mdm
labels:
app: fc-apple-mdm
app.kubernetes.io/name: fc-apple-mdm
app.kubernetes.io/component: mdm
app.kubernetes.io/part-of: flowercore
spec:
replicas: 1
revisionHistoryLimit: 3
strategy:
type: Recreate
selector:
matchLabels:
app: fc-apple-mdm
template:
metadata:
labels:
app: fc-apple-mdm
app.kubernetes.io/name: fc-apple-mdm
app.kubernetes.io/component: mdm
app.kubernetes.io/part-of: flowercore
annotations:
fc.flowercore.io/healthz-anon: "true"
fc.flowercore.io/probe-path: "/version"
flowercore.io/audit-trace-id: "apple-mdm-nanohub-runtime-trace"
flowercore.io/root-ca-sha256: "a9120c88fa3ec735d790aa4cfeb61ac2946730338969015bebaccc08fe10535e"
prometheus.io/scrape: "false"
spec:
enableServiceLinks: false
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
fsGroup: 1654
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: nanohub
image: localhost/fc-apple-mdm-nanohub:v0.2.0-20260617
imagePullPolicy: Never
ports:
- name: http
containerPort: 9004
protocol: TCP
env:
- name: HOME
value: "/var/lib/nanohub"
- name: NANOHUB_LISTEN
value: ":9004"
- name: NANOHUB_STORAGE
value: "file"
- name: NANOHUB_STORAGE_DSN
value: "/var/lib/nanohub/db"
- name: NANOHUB_CHECKIN
value: "true"
- name: NANOHUB_CA
value: "/etc/nanohub/ca/root_ca.crt"
- name: NANOHUB_API_KEY
valueFrom:
secretKeyRef:
name: fc-apple-mdm-runtime
key: NANOHUB_API_KEY
- name: NANOHUB_WEBHOOK_URL
valueFrom:
secretKeyRef:
name: fc-apple-mdm-runtime
key: NANOHUB_WEBHOOK_URL
optional: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
startupProbe:
httpGet:
path: /version
port: 9004
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
readinessProbe:
httpGet:
path: /version
port: 9004
periodSeconds: 10
failureThreshold: 3
livenessProbe:
tcpSocket:
port: 9004
initialDelaySeconds: 30
periodSeconds: 30
failureThreshold: 3
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /var/lib/nanohub
- name: tmp
mountPath: /tmp
- name: root-ca
mountPath: /etc/nanohub/ca
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: fc-apple-mdm-data
- name: tmp
emptyDir: {}
- name: root-ca
configMap:
name: fc-apple-mdm-root-ca
items:
- key: root_ca.crt
path: root_ca.crt
---
apiVersion: v1
kind: Service
metadata:
name: fc-apple-mdm
namespace: fc-apple-mdm
labels:
app: fc-apple-mdm
app.kubernetes.io/name: fc-apple-mdm
app.kubernetes.io/part-of: flowercore
spec:
type: ClusterIP
selector:
app: fc-apple-mdm
ports:
- name: http
port: 80
targetPort: 9004
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: fc-apple-mdm-scep
namespace: fc-apple-mdm
labels:
app: fc-apple-mdm-scep
app.kubernetes.io/name: fc-apple-mdm-scep
app.kubernetes.io/part-of: flowercore
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 9080
protocol: TCP
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: fc-apple-mdm-scep-noc1
namespace: fc-apple-mdm
labels:
kubernetes.io/service-name: fc-apple-mdm-scep
app.kubernetes.io/name: fc-apple-mdm-scep
app.kubernetes.io/part-of: flowercore
addressType: IPv4
endpoints:
- addresses:
- 10.0.56.10
conditions:
ready: true
ports:
- name: http
port: 9080
protocol: TCP
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: fc-apple-mdm-tls
namespace: fc-apple-mdm
annotations:
flowercore.io/dns-preflight: "mdm.iamworkin.lan must resolve to 10.0.56.200 before ACME sync"
spec:
secretName: fc-apple-mdm-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- mdm.iamworkin.lan
duration: 720h
renewBefore: 240h
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: fc-apple-mdm
namespace: fc-apple-mdm
spec:
entryPoints:
- websecure
routes:
- match: Host(`mdm.iamworkin.lan`) && PathPrefix(`/scep`)
kind: Rule
services:
- name: fc-apple-mdm-scep
port: 80
- match: Host(`mdm.iamworkin.lan`) && (PathPrefix(`/mdm`) || PathPrefix(`/checkin`) || PathPrefix(`/version`))
kind: Rule
services:
- name: fc-apple-mdm
port: 80
tls:
secretName: fc-apple-mdm-tls
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fc-apple-mdm-default-deny
namespace: fc-apple-mdm
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fc-apple-mdm-netpol
namespace: fc-apple-mdm
spec:
podSelector:
matchLabels:
app: fc-apple-mdm
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik-system
ports:
- port: 9004
protocol: TCP
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-devicemgmt
ports:
- port: 9004
protocol: TCP
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- port: 443
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-devicemgmt
ports:
- port: 80
protocol: TCP
- port: 8080
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fc-apple-mdm-acme-http-solver-allow
namespace: fc-apple-mdm
spec:
podSelector:
matchLabels:
acme.cert-manager.io/http01-solver: "true"
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- port: 8089
protocol: TCP
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

View File

@@ -0,0 +1,6 @@
# ArgoCD discovers apps-gx10/* directories on the GX10 GitOps branch.
# This kustomization is for local previews and single-app validation.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- fc-apple-mdm.yaml

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,187 @@
{
"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:v20260621-rum2-ace701d-db838f5",
"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"
}
],
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"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"
},
{
"mountPath": "/tmp",
"name": "temp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "chat-web-data"
}
},
{
"emptyDir": {},
"name": "temp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

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,331 @@
{
"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": {
"initContainers": [
{
"command": [
"sh",
"-c",
"chown -R 1654:1654 /app/data"
],
"image": "localhost/fc-remotedesktop-web:v20260621-rd-lsio-3d510aa",
"imagePullPolicy": "IfNotPresent",
"name": "fix-data-permissions",
"resources": {
"limits": {
"cpu": "100m",
"memory": "128Mi"
},
"requests": {
"cpu": "10m",
"memory": "32Mi"
}
},
"securityContext": {
"runAsGroup": 0,
"runAsUser": 0
},
"volumeMounts": [
{
"mountPath": "/app/data",
"name": "data"
}
]
}
],
"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:v20260621-rd-lsio-3d510aa",
"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,67 @@
# FlowerCore DeviceManagement on GX10
This adopted GX10 app hosts `FlowerCore.DeviceManagement.Web` at
`https://devices.iamworkin.lan`. Agent-only REST/SignalR callbacks can use
`https://devices-agent.iamworkin.lan`, which is a separate Traefik router that
requires a TLS client certificate and forwards the presented certificate to the
app. Traefik v3.6 currently forwards raw base64 DER in
`X-Forwarded-Tls-Client-Cert`; the app also accepts URL-escaped PEM for
compatibility with older/alternate Traefik shapes.
## Apple MDM Runtime Contract
Apple MDM is enabled in NanoHUB mode, but enrollment remains unavailable until
the runtime secret contains real Apple-side material. Do not use placeholder
values to clear readiness checks.
`Secret/fc-devicemgmt-runtime` supports these Apple MDM keys:
| Key | Purpose |
| --- | --- |
| `DEVICE_MANAGEMENT_OPERATOR_API_KEY` | Required operator API key for authenticated REST/MCP write operations, including Android command queueing. |
| `DEVICE_MANAGEMENT_ADMIN_API_KEY` | Required admin API key for privileged DeviceManagement operations. |
| `DEVICE_MANAGEMENT_AGENT_API_KEY` | Required scoped agent credential for REST agent callbacks when TLS terminates before Kestrel; maps to `Auth:AgentApiKey` and `FlowerCore:Auth:AgentApiKey`. |
| `DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM` | Optional persistent enrollment CA certificate PEM; maps to `FlowerCore:DeviceManagement:EnrollmentCertificateAuthorityCertificatePem`. Live on GX10 for the agent client-cert chain currently trusted by Traefik. |
| `DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM` | Optional private key PEM matching the persistent enrollment CA certificate; maps to `FlowerCore:DeviceManagement:EnrollmentCertificateAuthorityPrivateKeyPem`. |
| `NANOHUB_API_KEY` | NanoHUB API password for HTTP Basic user `nanohub`. |
| `APPLE_MDM_APNS_TOPIC` | MDM APNs topic returned after uploading the Apple MDM push certificate to NanoHUB/NanoMDM. |
| `APPLE_MDM_SCEP_URL` | Live SCEP URL included in the enrollment profile. |
| `APPLE_MDM_SCEP_CHALLENGE` | SCEP challenge shared with the SCEP provisioner. |
| `APPLE_MDM_PROFILE_SIGNING_CERTIFICATE_PEM` | PEM certificate used to CMS-sign `.mobileconfig` profiles. |
| `APPLE_MDM_PROFILE_SIGNING_PRIVATE_KEY_PEM` | PEM private key matching the profile-signing certificate. |
| `APPLE_MDM_REQUIRE_MANAGED_WIFI_PAYLOAD` | Set to `true` only when Wi-Fi payload delivery should gate enrollment readiness. |
| `APPLE_MDM_MANAGED_WIFI_SSID` | Managed Wi-Fi SSID for the iPad profile. |
| `APPLE_MDM_MANAGED_WIFI_PASSWORD` | Managed Wi-Fi password when the network is not open. |
Non-secret profile constants stay in GitOps: NanoHUB base URL, MDM server URL,
check-in URL, organization/display names, the HTTPS trust anchor certificate,
managed Wi-Fi encryption type, auto-join, and MAC-randomization disablement.
DeviceManagement auth is enabled on GX10. The deployment maps
`DEVICE_MANAGEMENT_OPERATOR_API_KEY` to both `Auth__ApiKey` and
`FlowerCore__Auth__ApiKey`; the unprefixed key keeps the MCP API key post-config
path aligned with REST auth. Agent heartbeat, inventory, command poll, app-catalog,
and command-result callbacks use the agent-specific authorization boundary: the
server validates a direct device client certificate when Kestrel receives one,
validates Traefik-forwarded client certificates only on
`devices-agent.iamworkin.lan`, and also accepts only the scoped
`DEVICE_MANAGEMENT_AGENT_API_KEY` via `Authorization: Bearer` or
`X-Agent-Api-Key` as the fallback path. Operator write endpoints must use
`X-Api-Key`.
The agent-only Traefik route uses `RequireAndVerifyClientCert` with
`Secret/devicemgmt-agent-client-ca`, derived from the persistent
DeviceManagement enrollment CA. The application still matches the forwarded
client certificate thumbprint to the enrolled device record, but unauthenticated
clients are now rejected during TLS before reaching the agent REST route.
## Readiness Check
After changing the runtime secret and letting the pod roll, verify:
```bash
curl -sk https://devices.iamworkin.lan/api/v1/apple-mdm/enrollment-profile/status
```
Configurator enrollment must wait until this status reports `available=true`
and an empty `missingRequirements` array.

View File

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

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,450 @@
{
"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__Auth__Enabled",
"value": "true"
},
{
"name": "Auth__ApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__Auth__ApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "Auth__AdminApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ADMIN_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__Auth__AdminApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ADMIN_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "Auth__AgentApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_AGENT_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__Auth__AgentApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_AGENT_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AgentMtls__ForwardedCertificateHosts__0",
"value": "devices-agent.iamworkin.lan"
},
{
"name": "FlowerCore__DeviceManagement__AgentMtls__ForwardedCertificateHeader",
"value": "X-Forwarded-Tls-Client-Cert"
},
{
"name": "FlowerCore__EventBus__Redis__Configuration",
"value": "redis.fc-redis.svc:6379"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__Enabled",
"value": "true"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__GatewayMode",
"value": "nanohub"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubBaseUrl",
"value": "http://fc-apple-mdm.fc-apple-mdm.svc"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubApiUserName",
"value": "nanohub"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubNanoMdmApiPath",
"value": "/api/v1/nanomdm/"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__EnrollmentProfileDownloadUrl",
"value": "https://devices.iamworkin.lan/api/v1/apple-mdm/enrollment-profile.mobileconfig"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__MdmServerUrl",
"value": "https://mdm.iamworkin.lan/mdm"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__MdmCheckInUrl",
"value": "https://mdm.iamworkin.lan/checkin"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__Organization",
"value": "FlowerCore"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__EnrollmentDisplayName",
"value": "FlowerCore Apple MDM"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepName",
"value": "FlowerCore Apple MDM Device Identity"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__TrustAnchorDisplayName",
"value": "IAmWorkin ACME CA Root CA"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__TrustAnchorCertificatePem",
"value": "-----BEGIN CERTIFICATE-----\nMIIBxDCCAWqgAwIBAgIRAPY357G6ow6zMAL5+4bS2kkwCgYIKoZIzj0EAwIwQDEa\nMBgGA1UEChMRSUFtV29ya2luIEFDTUUgQ0ExIjAgBgNVBAMTGUlBbVdvcmtpbiBB\nQ01FIENBIFJvb3QgQ0EwHhcNMjYwMzA4MTgwNzExWhcNMzYwMzA1MTgwNzExWjBA\nMRowGAYDVQQKExFJQW1Xb3JraW4gQUNNRSBDQTEiMCAGA1UEAxMZSUFtV29ya2lu\nIEFDTUUgQ0EgUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ2n04X1\nJZo5Zdq/i1Idv8+fqwZyAzBh7whbqj0SWsJL8UWRabCMqYCs7+dXO0xRSzqkwFDL\nx+vooOai8RgRNhajRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/\nAgEBMB0GA1UdDgQWBBRnuPPQR6iM/H6vOluiU3Sygayz8jAKBggqhkjOPQQDAgNI\nADBFAiEArQK9dYPGmAZsdYnjziuFVVE5NKZUcceYvGfGC+tLXUsCIAudF2zJrCRq\n3mK50ZZET/fwTkJwiEF4824mjP8p1CKM\n-----END CERTIFICATE-----"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "NANOHUB_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ApnsTopic",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_APNS_TOPIC",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepUrl",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_SCEP_URL",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepChallenge",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_SCEP_CHALLENGE",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ProfileSigningCertificatePem",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_PROFILE_SIGNING_CERTIFICATE_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ProfileSigningPrivateKeyPem",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_PROFILE_SIGNING_PRIVATE_KEY_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__RequireManagedWifiPayload",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_REQUIRE_MANAGED_WIFI_PAYLOAD",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiSsid",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_MANAGED_WIFI_SSID",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiPassword",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_MANAGED_WIFI_PASSWORD",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiEncryptionType",
"value": "WPA2"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiAutoJoin",
"value": "true"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiDisableAssociationMacRandomization",
"value": "true"
}
],
"image": "localhost/fc-devicemgmt-web:v20260621-bootstraptrust-df7d826",
"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,38 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "IngressRoute",
"metadata": {
"name": "devicemgmt-agent-mtls",
"namespace": "fc-devicemgmt"
},
"spec": {
"entryPoints": [
"websecure"
],
"routes": [
{
"kind": "Rule",
"match": "Host(`devices-agent.iamworkin.lan`)",
"middlewares": [
{
"name": "devicemgmt-agent-pass-client-cert",
"namespace": "fc-devicemgmt"
}
],
"services": [
{
"name": "fc-devicemgmt-web",
"port": 80
}
]
}
],
"tls": {
"options": {
"name": "devicemgmt-agent-mtls",
"namespace": "fc-devicemgmt"
},
"secretName": "fc-devicemgmt-agent-tls"
}
}
}

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,13 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "Middleware",
"metadata": {
"name": "devicemgmt-agent-pass-client-cert",
"namespace": "fc-devicemgmt"
},
"spec": {
"passTLSClientCert": {
"pem": true
}
}
}

View File

@@ -0,0 +1,20 @@
{
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": "devicemgmt-agent-client-ca",
"namespace": "fc-devicemgmt",
"labels": {
"app.kubernetes.io/name": "fc-devicemgmt-web",
"app.kubernetes.io/component": "agent-mtls",
"app.kubernetes.io/part-of": "flowercore",
"app.kubernetes.io/managed-by": "argocd",
"flowercore.io/tenant-id": "system",
"flowercore.io/created-by": "bluejay-infra"
}
},
"type": "Opaque",
"data": {
"ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUYyRENDQThDZ0F3SUJBZ0lVRTFhTVFrRXFmMzJMaVpwTHdudEd6NSsrWnBRd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2NqRUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQW9NQ2tac2IzZGxja052Y21VeEdqQVlCZ05WQkFzTQpFVVJsZG1salpTQk5ZVzVoWjJWdFpXNTBNVEl3TUFZRFZRUUREQ2xHYkc5M1pYSkRiM0psSUVSbGRtbGpaVTFoCmJtRm5aVzFsYm5RZ1JXNXliMnhzYldWdWRDQkRRVEFlRncweU5qQTJNVGt4TWpBM01UWmFGdzB6TmpBMk1UWXgKTWpBM01UWmFNSEl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRS0RBcEdiRzkzWlhKRGIzSmxNUm93R0FZRApWUVFMREJGRVpYWnBZMlVnVFdGdVlXZGxiV1Z1ZERFeU1EQUdBMVVFQXd3cFJteHZkMlZ5UTI5eVpTQkVaWFpwClkyVk5ZVzVoWjJWdFpXNTBJRVZ1Y205c2JHMWxiblFnUTBFd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUMKRHdBd2dnSUtBb0lDQVFDNGJLdWxXODV1alBFUmhGa2JZUmtDdHBmdWIrbnVvaHlPaW5lbWRXRERhQUpYQkVPVAoxNlUwc1dFODc0RmdCYzN3elYzQ0FUc3BZMW9VcGRyUUliQUZzWkN2VGZHSlhydmtJZm1yR0tmbGhZMHdZTTRKCm9OUFM2K1hYRlFWZVpzc3AwUnk1YWdLM3dUdE1uTFNuUkhzNm5VWnhIa2s1MzdaNER5NTAvT0xma2dYWnFyRm0KNStLaVJocHNTRytuUm12VmsrSW5yUmg1WS9HeENGN1ZWY2FJcStJRXVxZTIzc3ZMck8vZmJnb3k5MWlBMi9NUApWRWRuUFZ1cnAreFdONkswckZLZmE0QmU4RVhpRXZLK20reUttMEZTUEZ6ZFBON0xFWWhnRVJwYkp0S3RNQXJaCnVJc2FmeVl6M0c1REtBdllodlRyN3VCVnFOK1JQREtuWXNtcmdMcTQ0bXpESjkxcEMzckxOM0lHbXlVbloyZmwKWUc1UFIyNDBFOENOTVo0aHV1b3FZT3puYjJRUndPb1pmbG9rZDBISnJwVm4yUjNIVEpCL3ZacFMxbVlyNUMyTApBdTk2aHdvd0QvR1FlNzlyekRpa1lDempiQTFEZHFkeHZUb3g3Y1lRRUgzVUFZMEFGenpyUm5ISWRGYVgvZ2pyCm5GQy9od2RtQ3VZbm5KQ1Rpb3kzcVJjUFBPTTc4UzJrcGNJdy9MdjYrMDNlNFRUWjI2UVZIV3BQaUtNeHZiVjYKbk4xUUJBNGRjVUZtdzZXWFBIa0pFNnVVcHl5bEI1RzlmNkh6Rkt2TDhOZHNXRjA4WUc5SHc5d1lJTnpzbll3YQpDWkZJUmttenpQVERQZUZUUjUweVErWVRYY2FwZUpsWHQ4VjJRdzlQd0tWL0Rsa09QVE5YYkxDOTN3SURBUUFCCm8yWXdaREFkQmdOVkhRNEVGZ1FVcWgyOVpvUU9aNXRLY05hOEJjUnJISUthTHlnd0h3WURWUjBqQkJnd0ZvQVUKcWgyOVpvUU9aNXRLY05hOEJjUnJISUthTHlnd0VnWURWUjBUQVFIL0JBZ3dCZ0VCL3dJQkFEQU9CZ05WSFE4QgpBZjhFQkFNQ0FRWXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBS1dsbStkQ1BLZ2lDVUFrL3Zlb1RCV1dxMWFxClhJOC81WG9DQ2owU3Q0ZHdhbXdHVUxhU0J0cnZhNFRJMkY0UmpBU25uRmFIMkhlY3laWm5CZW4wZHUrUitDMzEKdmZkVHIrVEQ4QUlpQkEyZm13L1hvTkJwTEsrKzBNL2xoNjJ3ai80KzBlVGVJSHg4TENDazRxUWxUYUlrNld3bgpuK0JSL2F3ZGhRM2RMZzAvM1kzL2V2Q3FQS1B1OTQrc1d5YkpuMlNUb2l1Y1RhL0ZvbnBIWjdZSU9SM3N4b1JwClh1V2JyS3ZUMGFmL1pVeXVpeTJFd0Y1dGU4N25BMkp5OGVsdzFTeGMrM0RiYlc3M3JJMFhZTGxHa0M1NWhBeFUKa2ZYSE5EaXNvV1BoN1pBV09lVFk0OElPZGgvb1IraW9mbkdiYkFvekh0ZmtYOXdUN3V2RlpYS0xTbHM1SEg4dQpZcDRwazdZam9kdmFTbys5SDhua3lYOGxYNmI2RCs0Mi9xTkVVN1UxYjQrdHcxQ0QyVmlRRUFpMHdLbHgzRFZVCnluMm13Zk9SZjQzT1hVUjIzZHh2UXhEWDR0RUNKUVpxTWxJaFZsRmN4TWN6dzRNcTZiTVZTaVB0ZzZtcml0eEsKOUUrb3JsRnNQRm9GdlphSTlzVGUxQWpkZWlUbjhxdUgyUFZqZ3lEa2E0bmt1UG1JS0FIbDVWSWZtWnVjTndTaApxMVE0REo5OFVCQStlTkpuZ1RBYVlXY1BIa2R4U0xKV3EwVzU1NGFrZUZaUW5qYXg3NFZoQWtxdExEZlhnNWw1ClNUeTcxRDNDNnl4SUpyVmZzV1JLS3dDY2dOOWpWR2dNMHBXMVNRaGZmRDdwK1hNRHZxRDNiejQ5S09XM0VGVzAKcG9vaGY3UGl5eU9TYVh3UgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
}
}

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,16 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "TLSOption",
"metadata": {
"name": "devicemgmt-agent-mtls",
"namespace": "fc-devicemgmt"
},
"spec": {
"clientAuth": {
"clientAuthType": "RequireAndVerifyClientCert",
"secretNames": [
"devicemgmt-agent-client-ca"
]
}
}
}

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:v20260619-distribution-592ad75",
"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,178 @@
{
"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:v20260621-rum3-316cb2d",
"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": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"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: "0cdea666a09dea526fb701d06c2ce17b90664a0f"
spec:
serviceAccountName: dns-web
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
fsGroup: 1654
fsGroupChangePolicy: OnRootMismatch
containers:
- name: dns-web
image: localhost/fc-dns-web:v20260620-dns-sec5-93f96c6
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: "0cdea666a09dea526fb701d06c2ce17b90664a0f"
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-sec5-0cdea66
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,358 @@
# FlowerCore MCP Gateway for the GX10 cluster.
# Secret values are copied into Kubernetes Secrets out of band until the
# 1Password operator exists on GX10; never commit secret data here.
---
apiVersion: v1
kind: Namespace
metadata:
name: fc-gateway
labels:
app.kubernetes.io/part-of: flowercore
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: fc-gateway
namespace: fc-gateway
labels:
app.kubernetes.io/name: fc-gateway
app.kubernetes.io/part-of: flowercore
spec:
replicas: 1
revisionHistoryLimit: 3
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: fc-gateway
template:
metadata:
labels:
app.kubernetes.io/name: fc-gateway
app.kubernetes.io/part-of: flowercore
annotations:
fc.flowercore.io/healthz-anon: "true"
fc.flowercore.io/probe-path: "/healthz"
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics/prometheus"
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
fsGroup: 1654
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: localhost/fc-gateway:v20260621-chrome-9c860bb
imagePullPolicy: Never
ports:
- containerPort: 8080
name: http
env:
- name: ASPNETCORE_URLS
value: "http://+:8080"
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
value: "false"
- name: FlowerCore__Mcp__ApiKey__Key
valueFrom:
secretKeyRef:
name: gateway-mcp-keys
key: credential
- name: FlowerCore__Mcp__Gateway__Embedding__BaseUrl
value: "http://fc-llm-bridge.fc-llm-bridge.svc:8080/v1"
- name: FlowerCore__Mcp__Gateway__Embedding__Model
value: "fc:embedding"
- name: FlowerCore__Mcp__Gateway__Embedding__Mode
value: "openai"
- name: FlowerCore__Mcp__Gateway__Embedding__ApiKey
valueFrom:
secretKeyRef:
name: fc-llm-bridge-api-keys
key: agent-zero-k8s
optional: true
- name: GW_BACKEND_fc_mysql_KEY
valueFrom:
secretKeyRef:
name: mysql-mcp-keys
key: credential
optional: true
- name: GW_BACKEND_fc_php_KEY
valueFrom:
secretKeyRef:
name: php-mcp-keys
key: credential
optional: true
- name: GW_BACKEND_fc_telephony_KEY
valueFrom:
secretKeyRef:
name: telephony-mcp-keys
key: credential
optional: true
- name: GW_BACKEND_fc_chat_KEY
valueFrom:
secretKeyRef:
name: chat-mcp-keys
key: credential
optional: true
- name: GW_BACKEND_fc_dms_KEY
valueFrom:
secretKeyRef:
name: dms-mcp-keys
key: credential
optional: true
- name: GW_BACKEND_fc_knowledge_KEY
valueFrom:
secretKeyRef:
name: knowledge-mcp-tokens
key: password
optional: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 384Mi
volumeMounts:
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /home/app/logs
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
startupProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: fc-gateway
namespace: fc-gateway
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: fc-gateway
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: fc-gateway-tls
namespace: fc-gateway
spec:
secretName: fc-gateway-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- gateway.iamworkin.lan
duration: 720h
renewBefore: 240h
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: fc-gateway
namespace: fc-gateway
spec:
entryPoints:
- websecure
routes:
- match: Host(`gateway.iamworkin.lan`)
kind: Rule
services:
- name: fc-gateway
port: 80
tls:
secretName: fc-gateway-tls
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fc-gateway-default-deny
namespace: fc-gateway
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fc-gateway-netpol
namespace: fc-gateway
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: fc-gateway
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: agent-zero
ports:
- port: 8080
protocol: TCP
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik-system
ports:
- port: 8080
protocol: TCP
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- port: 8080
protocol: TCP
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-llm-bridge
ports:
- port: 8080
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-mysql
ports:
- port: 5300
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-php
ports:
- port: 5400
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: telephony
ports:
- port: 5100
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-chat
ports:
- port: 80
protocol: TCP
- port: 8080
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: fc-dms
ports:
- port: 80
protocol: TCP
- port: 8080
protocol: TCP
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: knowledge
ports:
- port: 80
protocol: TCP
- port: 8080
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fc-gateway-acme-http-solver-allow
namespace: fc-gateway
spec:
podSelector:
matchLabels:
acme.cert-manager.io/http01-solver: "true"
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- port: 8089
protocol: TCP
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP

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,119 @@
{
"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:v20260619-library-0e027cc",
"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": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"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:v20260621-rum2-ace701d-f2b7d3e",
"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,273 @@
{
"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:v20260620-media-chrome-e05b9c1",
"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"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"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"
},
{
"mountPath": "/tmp",
"name": "temp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"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"
}
},
{
"emptyDir": {},
"name": "temp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

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,143 @@
{
"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:v20260620-menuboard-chrome-b36eb7a",
"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": {},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "temp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "menuboard-web-data"
}
},
{
"emptyDir": {},
"name": "temp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

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,149 @@
{
"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:v20260620-messageboard-chrome-e652648",
"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"
},
{
"mountPath": "/app/logs",
"name": "logs"
},
{
"mountPath": "/tmp",
"name": "tmp"
}
],
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
}
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "messageboard-web-data"
}
},
{
"name": "logs",
"emptyDir": {}
},
{
"name": "tmp",
"emptyDir": {}
}
]
}
}
}
}

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,220 @@
{
"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__Mcp__ApiKey__Key",
"valueFrom": {
"secretKeyRef": {
"key": "credential",
"name": "mysql-mcp-keys",
"optional": true
}
}
},
{
"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:v20260618-hm4-tenant-84dc65c",
"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:v20260621-network-chrome-1375ad7",
"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": "{\"PhpManager\":{\"Namespace\":\"fc-php\",\"Slowlog\":{\"Path\":\"/var/log/apache2/php-fpm-slow.log\",\"Sidecar\":{\"Enabled\":true,\"Image\":\"\"}},\"PoolConfig\":{\"StartServers\":null,\"MinSpareServers\":null,\"MaxSpareServers\":null,\"ProcessIdleTimeoutSeconds\":10,\"RequestTerminateTimeoutSeconds\":30},\"Certificates\":{\"TlsInspector\":{\"LogGracefulDegradeWarnings\":false}},\"Backups\":{\"StoragePath\":\"/data/backups\"},\"Ingress\":{\"DefaultMiddlewares\":[{\"Name\":\"php-tenant-rate-limit\",\"Namespace\":\"fc-php\"},{\"Name\":\"php-tenant-secure-headers\",\"Namespace\":\"fc-php\"}],\"TlsOption\":{\"Name\":\"php-tenant-tls13\",\"Namespace\":\"fc-php\"},\"Waf\":{\"Enabled\":true,\"Image\":\"owasp/modsecurity-crs:4.25-nginx-alpine-lts@sha256:88b59911549723e71beabf3b4aa47bbd31b00e79401f442e65ddfc430ae46343\",\"AllowedMethods\":\"GET HEAD POST OPTIONS DELETE\"}}},\"ApplicationArchives\":{\"WordPressCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/wordpress/latest.tar.gz\",\"WordPressProxySourceUrl\":\"https://wordpress.org/latest.tar.gz\",\"WordPressLocalArchivePath\":\"/data/application-archives/latest.tar.gz\",\"MyBbCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mybb/latest.zip\",\"MyBbProxySourceUrl\":\"https://mybb.com/download/\",\"MyBbLocalArchivePath\":\"/data/application-archives/mybb-latest.zip\",\"MediaWikiCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/mediawiki/latest.tar.gz\",\"MediaWikiProxySourceUrl\":\"https://releases.wikimedia.org/mediawiki/1.45/mediawiki-1.45.3.tar.gz\",\"MediaWikiLocalArchivePath\":\"/data/application-archives/mediawiki-latest.tar.gz\",\"DrupalCoreUrl\":\"http://php-web.fc-php.svc.cluster.local.:5400/api/v1/application-archives/drupal/latest.tar.gz\",\"DrupalProxySourceUrl\":\"https://ftp.drupal.org/files/projects/drupal-11.3.8.tar.gz\",\"DrupalLocalArchivePath\":\"/data/application-archives/drupal-latest.tar.gz\",\"BypassUpstreamTls\":true},\"ContainerBackend\":{\"Default\":\"Kubernetes\"},\"FlowerCore\":{\"Auth\":{\"Provider\":\"Oidc\",\"Enabled\":false,\"Oidc\":{\"Enabled\":true,\"Authority\":\"https://id.iamworkin.lan/application/o/php/\",\"Audience\":\"php\",\"ClientId\":\"php\",\"ClientSecret\":\"\"},\"Impersonation\":{\"Enabled\":false,\"DebugMode\":false}},\"Tenant\":{\"StrictMode\":false,\"JwtClaimsEnabled\":false,\"TenantClaimType\":\"fc:tenant\",\"ActorIdClaimType\":\"flowercore_actor_id\"},\"Account\":{\"AppId\":\"php\",\"DefaultTenantId\":\"default\",\"Impersonation\":{\"Enabled\":false,\"StrictMode\":false,\"TechSupportRoles\":[\"tech-support\"],\"Targets\":[]}},\"Hosting\":{\"AutoDns\":{\"Enabled\":true,\"DnsManagerBaseUrl\":\"https://dns.iamworkin.lan/\",\"ZoneName\":\"iamworkin.lan\",\"RecordType\":\"A\",\"TargetAddress\":\"10.0.57.202\",\"Ttl\":300,\"BypassTls\":true}},\"Database\":{\"Provider\":\"Sqlite\",\"ConnectionStrings\":{\"Sqlite\":\"Data Source=/data/php-manager.db\"}}}}"
},
"kind": "ConfigMap",
"metadata": {
"name": "php-web-config",
"namespace": "fc-php"
}
}

View File

@@ -0,0 +1,186 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "flowercore",
"app.kubernetes.io/name": "php-waf"
},
"name": "php-waf",
"namespace": "fc-php"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "php-waf"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "php-waf"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "BACKEND",
"value": "http://php-web.fc-php.svc.cluster.local:5400"
},
{
"name": "SERVER_NAME",
"value": "php.iamworkin.lan"
},
{
"name": "PORT",
"value": "8080"
},
{
"name": "PROXY_PRESERVE_HOST",
"value": "on"
},
{
"name": "PROXY_TIMEOUT",
"value": "60s"
},
{
"name": "MODSEC_RULE_ENGINE",
"value": "On"
},
{
"name": "MODSEC_AUDIT_ENGINE",
"value": "RelevantOnly"
},
{
"name": "MODSEC_AUDIT_LOG",
"value": "/dev/stdout"
},
{
"name": "MODSEC_AUDIT_LOG_TYPE",
"value": "Serial"
},
{
"name": "ALLOWED_METHODS",
"value": "GET HEAD POST OPTIONS DELETE"
},
{
"name": "LOGLEVEL",
"value": "warn"
},
{
"name": "ERRORLOG",
"value": "/dev/stderr"
},
{
"name": "ACCESSLOG",
"value": "/dev/stdout"
},
{
"name": "BLOCKING_PARANOIA",
"value": "1"
},
{
"name": "DETECTION_PARANOIA",
"value": "1"
},
{
"name": "ANOMALY_INBOUND",
"value": "5"
},
{
"name": "ANOMALY_OUTBOUND",
"value": "4"
}
],
"image": "owasp/modsecurity-crs:4.25-nginx-alpine-lts@sha256:88b59911549723e71beabf3b4aa47bbd31b00e79401f442e65ddfc430ae46343",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"httpHeaders": [
{
"name": "Host",
"value": "php.iamworkin.lan"
}
],
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 20,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 2
},
"name": "php-waf",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"httpHeaders": [
{
"name": "Host",
"value": "php.iamworkin.lan"
}
],
"path": "/healthz",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 2
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"enableServiceLinks": false,
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 101,
"runAsGroup": 101,
"runAsNonRoot": true,
"runAsUser": 101
},
"serviceAccount": "php-web",
"serviceAccountName": "php-web",
"terminationGracePeriodSeconds": 30
}
}
}
}

View File

@@ -0,0 +1,219 @@
{
"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-19T00:00:00-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__Mcp__ApiKey__Key",
"valueFrom": {
"secretKeyRef": {
"key": "credential",
"name": "php-mcp-keys",
"optional": true
}
}
},
{
"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:v20260619-whc4-generated-waf-147f02a",
"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-waf",
"port": 8080
}
]
}
],
"tls": {
"secretName": "php-web-tls"
}
}
}

View File

@@ -0,0 +1,15 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "Middleware",
"metadata": {
"name": "php-tenant-rate-limit",
"namespace": "fc-php"
},
"spec": {
"rateLimit": {
"average": 120,
"burst": 240,
"period": "1m"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "Middleware",
"metadata": {
"name": "php-tenant-secure-headers",
"namespace": "fc-php"
},
"spec": {
"headers": {
"contentTypeNosniff": true,
"browserXssFilter": true,
"referrerPolicy": "strict-origin-when-cross-origin",
"stsSeconds": 31536000,
"stsIncludeSubdomains": true,
"stsPreload": false
}
}
}

View File

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

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,11 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "TLSOption",
"metadata": {
"name": "php-tenant-tls13",
"namespace": "fc-php"
},
"spec": {
"minVersion": "VersionTLS13"
}
}

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,161 @@
{
"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:v20260619-presentations-a67ef22",
"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": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"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:v20260619-presentations-a67ef22",
"imagePullPolicy": "Never",
"name": "storage-init",
"resources": {
"limits": {
"cpu": "250m",
"memory": "256Mi"
},
"requests": {
"cpu": "25m",
"memory": "64Mi"
}
},
"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,120 @@
{
"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:v20260619-retail-faae9db",
"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": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"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:v20260620-scoreboard-vr-5df3f9a",
"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:v20260620-scoreboard-vr-5df3f9a",
"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,147 @@
{
"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:v20260621-segmentdisplay-chrome-f059aa2",
"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": {},
"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/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "segmentdisplay-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

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:v20260619-signage-replay-4da0983",
"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:v20260620-signage-sec5-5ee8f74",
"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"
}
}
}

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