Compare commits

...

172 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
81 changed files with 3007 additions and 253 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

@@ -44,7 +44,7 @@
}
}
],
"image": "localhost/fc-aistation-web:v20260616-ai6-gx10-websurface-a8a3e9d-2f9f89e",
"image": "localhost/fc-aistation-web:v20260619-aistation-eb9d513",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -78,7 +78,16 @@
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"resources": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [

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

@@ -83,7 +83,7 @@
}
}
],
"image": "localhost/fc-chat-web:v20260617-chatfix-54fd549",
"image": "localhost/fc-chat-web:v20260621-rum2-ace701d-db838f5",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -98,22 +98,22 @@
"timeoutSeconds": 5
},
"name": "chat-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
@@ -138,49 +138,49 @@
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "temp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
"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
},
"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"
}
]
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "chat-web-data"
}
},
{
"emptyDir": {},
"name": "temp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}

View File

@@ -27,10 +27,42 @@
"app.kubernetes.io/name": "remotedesktop-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
},
"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",
@@ -200,7 +232,7 @@
}
}
],
"image": "localhost/fc-remotedesktop-web:gx10-v1",
"image": "localhost/fc-remotedesktop-web:v20260621-rd-lsio-3d510aa",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 3,

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

@@ -88,21 +88,260 @@
"name": "FlowerCore__Database__ConnectionStrings__Sqlite",
"value": "Data Source=/data/devicemgmt.db"
},
{
"name": "FlowerCore__Database__Password",
"valueFrom": {
"secretKeyRef": {
"key": "DB-Password",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__EventBus__Redis__Configuration",
"value": "redis.fc-redis.svc:6379"
}
],
"image": "localhost/fc-devicemgmt-web:v20260617-an13-b9c79c4",
{
"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,

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,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,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

@@ -113,7 +113,7 @@
"value": "*"
}
],
"image": "localhost/fc-distribution:gx10-v1",
"image": "localhost/fc-distribution:v20260619-distribution-592ad75",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -97,7 +97,7 @@
}
}
],
"image": "localhost/fc-dms-web:gx10-v1",
"image": "localhost/fc-dms-web:v20260621-rum3-316cb2d",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -136,7 +136,16 @@
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"resources": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [

View File

@@ -148,7 +148,7 @@ spec:
fsGroupChangePolicy: OnRootMismatch
containers:
- name: dns-web
image: localhost/fc-dns-web:v20260617-sec5-0cdea66
image: localhost/fc-dns-web:v20260620-dns-sec5-93f96c6
imagePullPolicy: Never
securityContext:
readOnlyRootFilesystem: true

View File

@@ -8,6 +8,12 @@ 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
@@ -43,9 +49,11 @@ spec:
runAsGroup: 1654
fsGroup: 1654
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: localhost/fc-gateway:v20260617-hm1-gateway-e0627e3
image: localhost/fc-gateway:v20260621-chrome-9c860bb
imagePullPolicy: Never
ports:
- containerPort: 8080
@@ -203,6 +211,17 @@ spec:
---
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
@@ -300,3 +319,40 @@ spec:
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

@@ -46,7 +46,7 @@
}
}
],
"image": "localhost/fc-library-web:gx10-v1",
"image": "localhost/fc-library-web:v20260619-library-0e027cc",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -80,7 +80,16 @@
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"resources": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [

View File

@@ -176,7 +176,7 @@
"value": "00:05:00"
}
],
"image": "localhost/fc-llm-bridge:gx10-v2",
"image": "localhost/fc-llm-bridge:v20260621-rum2-ace701d-f2b7d3e",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -92,7 +92,7 @@
}
}
],
"image": "localhost/fc-media-web:v20260617-sec5-media-f9228d2",
"image": "localhost/fc-media-web:v20260620-media-chrome-e05b9c1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -48,7 +48,7 @@
}
}
],
"image": "localhost/fc-menuboard-web:v20260617-sec5-menuboard-303a636",
"image": "localhost/fc-menuboard-web:v20260620-menuboard-chrome-b36eb7a",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -49,7 +49,7 @@
}
}
],
"image": "localhost/fc-messageboard-web:v20260617-sec5-messageboard-e5f77ef",
"image": "localhost/fc-messageboard-web:v20260620-messageboard-chrome-e652648",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -86,7 +86,7 @@
"value": "mysql"
}
],
"image": "localhost/fc-mysql-web:v20260617-sec4-storage-6fc3739",
"image": "localhost/fc-mysql-web:v20260618-hm4-tenant-84dc65c",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -95,7 +95,7 @@
"value": "/data/intended.json"
}
],
"image": "localhost/fc-network-web:v20260617-sec5-network-e199147",
"image": "localhost/fc-network-web:v20260621-network-chrome-1375ad7",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -1,7 +1,7 @@
{
"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\"}}},\"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.56.200\",\"Ttl\":300,\"BypassTls\":true}},\"Database\":{\"Provider\":\"Sqlite\",\"ConnectionStrings\":{\"Sqlite\":\"Data Source=/data/php-manager.db\"}}}}"
"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": {

View File

@@ -67,6 +67,10 @@
"name": "MODSEC_AUDIT_LOG_TYPE",
"value": "Serial"
},
{
"name": "ALLOWED_METHODS",
"value": "GET HEAD POST OPTIONS DELETE"
},
{
"name": "LOGLEVEL",
"value": "warn"

View File

@@ -24,7 +24,7 @@
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-06-13T01:59:27-05:00",
"kubectl.kubernetes.io/restartedAt": "2026-06-19T00:00:00-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5400",
"prometheus.io/scrape": "true"
@@ -86,7 +86,7 @@
"value": "php"
}
],
"image": "localhost/fc-php-web:v20260617-whc4-edge-638b3b3",
"image": "localhost/fc-php-web:v20260619-whc4-generated-waf-147f02a",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -49,7 +49,7 @@
}
}
],
"image": "localhost/fc-presentations:gx10-v1",
"image": "localhost/fc-presentations:v20260619-presentations-a67ef22",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -83,7 +83,16 @@
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"resources": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
@@ -108,10 +117,19 @@
"-lc",
"set -eu\nmkdir -p /mnt/pvc/data /mnt/pvc/uploads\n\nfor file in presentations.db presentations.db-shm presentations.db-wal; do\n if [ -f \"/mnt/pvc/${file}\" ] && [ ! -f \"/mnt/pvc/data/${file}\" ]; then\n mv \"/mnt/pvc/${file}\" \"/mnt/pvc/data/${file}\"\n fi\ndone\n\nif [ -d /mnt/pvc/dp-keys ] && [ ! -d /mnt/pvc/data/dp-keys ]; then\n mv /mnt/pvc/dp-keys /mnt/pvc/data/dp-keys\nfi\n\nfor directory in imports slides html-bundles; do\n if [ -d \"/mnt/pvc/${directory}\" ] && [ ! -d \"/mnt/pvc/uploads/${directory}\" ]; then\n mv \"/mnt/pvc/${directory}\" \"/mnt/pvc/uploads/${directory}\"\n fi\ndone\n\nmkdir -p \\\n /mnt/pvc/data/dp-keys \\\n /mnt/pvc/uploads/imports \\\n /mnt/pvc/uploads/slides \\\n /mnt/pvc/uploads/html-bundles\n"
],
"image": "localhost/fc-presentations:gx10-v1",
"image": "localhost/fc-presentations:v20260619-presentations-a67ef22",
"imagePullPolicy": "Never",
"name": "storage-init",
"resources": {},
"resources": {
"limits": {
"cpu": "250m",
"memory": "256Mi"
},
"requests": {
"cpu": "25m",
"memory": "64Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [

View File

@@ -47,7 +47,7 @@
}
}
],
"image": "localhost/fc-retail-web:gx10-v1",
"image": "localhost/fc-retail-web:v20260619-retail-faae9db",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -81,7 +81,16 @@
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"resources": {
"limits": {
"cpu": "1",
"memory": "1Gi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [

View File

@@ -44,7 +44,7 @@
}
}
],
"image": "localhost/fc-scoreboard-web:gx10-v1",
"image": "localhost/fc-scoreboard-web:v20260620-scoreboard-vr-5df3f9a",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -93,7 +93,7 @@
"-c",
"chown -R 1654:1654 /data && chmod -R u+rwX,g+rwX /data"
],
"image": "localhost/fc-scoreboard-web:gx10-v1",
"image": "localhost/fc-scoreboard-web:v20260620-scoreboard-vr-5df3f9a",
"imagePullPolicy": "Never",
"name": "scoreboard-data-permissions",
"resources": {},

View File

@@ -49,7 +49,7 @@
}
}
],
"image": "localhost/fc-segmentdisplay-web:v20260617-sec5-segmentdisplay-7730fb2",
"image": "localhost/fc-segmentdisplay-web:v20260621-segmentdisplay-chrome-f059aa2",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -58,7 +58,7 @@
"value": "http://dms-web.fc-dms.svc.cluster.local.:8081"
}
],
"image": "localhost/fc-signage-replay-web:gx10-v1",
"image": "localhost/fc-signage-replay-web:v20260619-signage-replay-4da0983",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -98,7 +98,7 @@
"value": "120"
}
],
"image": "localhost/fc-signage-web:v20260617-gx10-f2-4da0983",
"image": "localhost/fc-signage-web:v20260620-signage-sec5-5ee8f74",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -106,7 +106,7 @@
}
}
],
"image": "localhost/fc-kiosk-web:gx10-v1",
"image": "localhost/fc-kiosk-web:v20260619-kiadmin-7cc83fd",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 6,
@@ -195,7 +195,7 @@
"-c",
"mkdir -p /profiles/data && chown -R 1654:1654 /profiles/data && chmod -R u+rwX,g+rwX /profiles/data"
],
"image": "localhost/fc-kiosk-web:gx10-v1",
"image": "localhost/fc-kiosk-web:v20260619-kiadmin-7cc83fd",
"imagePullPolicy": "Never",
"name": "fix-profile-perms",
"resources": {},

View File

@@ -66,11 +66,11 @@
},
{
"name": "PhpManager__BaseUrl",
"value": "https://php.iamworkin.lan/"
"value": "http://php-web.fc-php.svc.cluster.local:5400/"
},
{
"name": "PhpManager__BypassTls",
"value": "true"
"value": "false"
}
],
"image": "localhost/fc-php-operator:v20260617-sec5-0bfbf42",

View File

@@ -1,8 +1,8 @@
{
"apiVersion": "v1",
"data": {
"default.conf": "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n location / { try_files $uri $uri/ =404; }\n location /healthz { access_log off; return 200 \"ok\"; add_header Content-Type text/plain; }\n}\n"
},
"default.conf": "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n location / { try_files $uri $uri/ =404; }\n location = /lamp-canary/index.php { add_header Content-Type text/plain; return 200 \"lamp-index-ok\\n\"; }\n location = /lamp-canary/wp-login.php { add_header Content-Type text/plain; return 200 \"wp-login-ok\\n\"; }\n location = /lamp-canary/mediawiki/index.php { add_header Content-Type text/plain; return 200 \"mediawiki-ok\\n\"; }\n location = /admin-allowlist-proof { add_header Content-Type text/plain; return 200 \"admin-allowlist-ok\\n\"; }\n location /healthz { access_log off; return 200 \"ok\"; add_header Content-Type text/plain; }\n}\n"
},
"kind": "ConfigMap",
"metadata": {
"name": "andrew-web-nginx-conf",

View File

@@ -24,12 +24,15 @@
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"labels": {
"app": "andrew-web"
}
},
"template": {
"metadata": {
"annotations": {
"flowercore.io/config-revision": "whc4-lamp-allowlist-20260618"
},
"labels": {
"app": "andrew-web"
}
},
"spec": {
"containers": [
{

View File

@@ -11,8 +11,18 @@
],
"routes": [
{
"kind": "Rule",
"match": "Host(`bluejay.dev`) || Host(`www.bluejay.dev`)",
"kind": "Rule",
"match": "Host(`bluejay.dev`) || Host(`www.bluejay.dev`)",
"middlewares": [
{
"name": "andrew-tenant-rate-limit",
"namespace": "fc-tenant-andrew"
},
{
"name": "andrew-tenant-secure-headers",
"namespace": "fc-tenant-andrew"
}
],
"priority": 100,
"services": [
{
@@ -20,10 +30,39 @@
"port": 8080
}
]
},
{
"kind": "Rule",
"match": "(Host(`bluejay.dev`) || Host(`www.bluejay.dev`)) && PathPrefix(`/admin-allowlist-proof`)",
"middlewares": [
{
"name": "andrew-admin-ip-allowlist",
"namespace": "fc-tenant-andrew"
},
{
"name": "andrew-tenant-rate-limit",
"namespace": "fc-tenant-andrew"
},
{
"name": "andrew-tenant-secure-headers",
"namespace": "fc-tenant-andrew"
}
],
"priority": 300,
"services": [
{
"name": "andrew-web-waf",
"port": 8080
}
]
}
],
"tls": {
"secretName": "cf-origin-bluejay-dev"
}
}
}
],
"tls": {
"options": {
"name": "andrew-tenant-tls13",
"namespace": "fc-tenant-andrew"
},
"secretName": "cf-origin-bluejay-dev"
}
}
}

View File

@@ -0,0 +1,15 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "Middleware",
"metadata": {
"name": "andrew-admin-ip-allowlist",
"namespace": "fc-tenant-andrew"
},
"spec": {
"ipAllowList": {
"sourceRange": [
"10.0.56.14/32"
]
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "TLSOption",
"metadata": {
"name": "andrew-tenant-tls13",
"namespace": "fc-tenant-andrew"
},
"spec": {
"minVersion": "VersionTLS13"
}
}

View File

@@ -0,0 +1,11 @@
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "flowercore",
"flowercore.io/tenant": "default"
},
"name": "fc-tenant-default"
}
}

View File

@@ -0,0 +1,177 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-align",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "ttsreader-align",
"namespace": "fc-ttsreader"
},
"spec": {
"progressDeadlineSeconds": 900,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "ttsreader-align"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/health",
"prometheus.io/path": "/health",
"prometheus.io/port": "9200",
"prometheus.io/scrape": "false"
},
"labels": {
"app.kubernetes.io/name": "ttsreader-align",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "WHISPER_MODEL",
"value": "Systran/faster-whisper-base.en"
},
{
"name": "WHISPER_CACHE_DIR",
"value": "/models"
},
{
"name": "HF_HOME",
"value": "/models/huggingface"
},
{
"name": "HF_HUB_CACHE",
"value": "/models/huggingface/hub"
},
{
"name": "XDG_CACHE_HOME",
"value": "/models/.cache"
},
{
"name": "HF_HUB_DISABLE_XET",
"value": "1"
},
{
"name": "WHISPER_DEVICE",
"value": "cpu"
},
{
"name": "WHISPER_COMPUTE_TYPE",
"value": "int8"
},
{
"name": "DEFAULT_LANGUAGE",
"value": "en"
},
{
"name": "MAX_AUDIO_BYTES",
"value": "52428800"
}
],
"image": "localhost/fc-speech-align:v20260619-aiabs2",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 9200,
"scheme": "HTTP"
},
"initialDelaySeconds": 120,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "align",
"ports": [
{
"containerPort": 9200,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 18,
"httpGet": {
"path": "/health",
"port": 9200,
"scheme": "HTTP"
},
"initialDelaySeconds": 20,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "2",
"memory": "3Gi"
},
"requests": {
"cpu": "250m",
"memory": "1Gi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/models",
"name": "data",
"subPath": "speech-align-models"
},
{
"mountPath": "/tmp",
"name": "tmp"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "ttsreader-data"
}
},
{
"emptyDir": {},
"name": "tmp"
}
]
}
}
}
}

View File

@@ -0,0 +1,92 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-biblical",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "ttsreader-biblical",
"namespace": "fc-ttsreader"
},
"spec": {
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "ttsreader-biblical"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-biblical",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"image": "localhost/fc-biblical-tts:v20260619-aiabs2",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 10402,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "biblical-tts",
"ports": [
{
"containerPort": 10402,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 10402,
"scheme": "HTTP"
},
"initialDelaySeconds": 5,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "1000m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30
}
}
}
}

View File

@@ -0,0 +1,104 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-kokoro",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "ttsreader-kokoro",
"namespace": "fc-ttsreader"
},
"spec": {
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "ttsreader-kokoro"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-kokoro",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"image": "ghcr.io/remsky/kokoro-fastapi-cpu:latest",
"imagePullPolicy": "IfNotPresent",
"livenessProbe": {
"failureThreshold": 5,
"httpGet": {
"path": "/v1/audio/voices",
"port": 8880,
"scheme": "HTTP"
},
"initialDelaySeconds": 180,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 15
},
"name": "kokoro",
"ports": [
{
"containerPort": 8880,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 18,
"httpGet": {
"path": "/v1/audio/voices",
"port": 8880,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "2000m",
"memory": "3Gi"
},
"requests": {
"cpu": "250m",
"memory": "1Gi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}
],
"dnsConfig": {
"nameservers": [
"10.43.0.10"
],
"options": [
{
"name": "ndots",
"value": "2"
}
],
"searches": [
"fc-ttsreader.svc.cluster.local",
"svc.cluster.local",
"cluster.local"
]
},
"dnsPolicy": "None",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"terminationGracePeriodSeconds": 30
}
}
}
}

View File

@@ -197,7 +197,7 @@
}
}
],
"image": "localhost/fc-ttsreader-web:v20260616-quicknav-b8c6174",
"image": "localhost/fc-ttsreader-web:v20260620-ttsreader-vr-a3b3957",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -0,0 +1,26 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-align",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "ttsreader-align",
"namespace": "fc-ttsreader"
},
"spec": {
"ports": [
{
"name": "http",
"port": 9200,
"protocol": "TCP",
"targetPort": 9200
}
],
"selector": {
"app.kubernetes.io/name": "ttsreader-align"
},
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,26 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-biblical",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "ttsreader-biblical",
"namespace": "fc-ttsreader"
},
"spec": {
"ports": [
{
"name": "http",
"port": 10402,
"protocol": "TCP",
"targetPort": 10402
}
],
"selector": {
"app.kubernetes.io/name": "ttsreader-biblical"
},
"type": "ClusterIP"
}
}

View File

@@ -0,0 +1,26 @@
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app.kubernetes.io/name": "ttsreader-kokoro",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "ttsreader-kokoro",
"namespace": "fc-ttsreader"
},
"spec": {
"ports": [
{
"name": "http",
"port": 8880,
"protocol": "TCP",
"targetPort": 8880
}
],
"selector": {
"app.kubernetes.io/name": "ttsreader-kokoro"
},
"type": "ClusterIP"
}
}

View File

@@ -53,13 +53,17 @@
"name": "FlowerCore__Updater__BundleStorage__LocalFs__RootDirectory",
"value": "/data/bundles"
},
{
"name": "FlowerCore__Updater__PublicShares__RequirePublicVisibilityOnPublicHosts",
"value": "true"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__0__Code",
"value": "8f3c2a9e7d41"
{
"name": "FlowerCore__Updater__PublicShares__RequirePublicVisibilityOnPublicHosts",
"value": "true"
},
{
"name": "FlowerCore__Updater__PublicShares__RequireShareLinkOnPublicHosts",
"value": "true"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__0__Code",
"value": "8f3c2a9e7d41"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__0__AppId",
@@ -81,14 +85,46 @@
"name": "FlowerCore__Updater__PublicShares__Links__0__Headline",
"value": "Faith AI Mike Edition"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__0__Description",
"value": "Private release link for Mike's Faith AI bundle."
},
{
"name": "FlowerCore__Audit__Sinks__Loki__Enabled",
"value": "false"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__0__Description",
"value": "Private release link for Mike's Faith AI bundle."
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__RoutePrefix",
"value": "kiosk-bundle"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__Code",
"value": "b22f04a5ff39"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__AppId",
"value": "flowercore.kiosk"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__Channel",
"value": "stable"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__RuntimeId",
"value": "win-x64"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__DisplayName",
"value": "FlowerCore Kiosk"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__Headline",
"value": "FlowerCore Kiosk"
},
{
"name": "FlowerCore__Updater__PublicShares__Links__1__Description",
"value": "Private release link for the FlowerCore Kiosk Windows installer."
},
{
"name": "FlowerCore__Audit__Sinks__Loki__Enabled",
"value": "false"
},
{
"name": "FlowerCore__Updater__Auth__Bootstrap__Enabled",
"value": "true"
@@ -195,7 +231,7 @@
"value": "26843545600"
}
],
"image": "localhost/fc-updater-web:v20260617-sec5-913c6a9",
"image": "localhost/fc-updater-web:v20260620-updater-sec5-f20976e",
"imagePullPolicy": "Never",
"securityContext": {
"allowPrivilegeEscalation": false,

View File

@@ -12,7 +12,7 @@
"routes": [
{
"kind": "Rule",
"match": "(Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))",
"match": "(Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`))",
"priority": 100,
"services": [
{

View File

@@ -101,7 +101,7 @@
"value": "false"
}
],
"image": "localhost/fc-worldbuilder:gx10-v1",
"image": "localhost/fc-worldbuilder:v20260620-chrome-94c6d42",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -187,10 +187,13 @@
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{

View File

@@ -22,8 +22,10 @@
]
}
],
"tls": {
"secretName": "worldbuilder-web-tls"
}
}
}
"tls": {
"store": {
"name": "default"
}
}
}
}

View File

@@ -0,0 +1,20 @@
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-worldbuilder",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system",
"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"
},
"name": "fc-worldbuilder"
}
}

View File

@@ -0,0 +1,15 @@
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "fc-worldbuilder-default-deny",
"namespace": "fc-worldbuilder"
},
"spec": {
"podSelector": {},
"policyTypes": [
"Ingress",
"Egress"
]
}
}

View File

@@ -0,0 +1,93 @@
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "worldbuilder-web",
"namespace": "fc-worldbuilder"
},
"spec": {
"podSelector": {
"matchLabels": {
"app.kubernetes.io/name": "worldbuilder-web"
}
},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [
{
"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": [
{
"ipBlock": {
"cidr": "10.0.56.20/32"
}
}
],
"ports": [
{
"port": 8188,
"protocol": "TCP"
}
]
}
]
}
}

View File

@@ -0,0 +1,28 @@
{
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"metadata": {
"labels": {
"app.kubernetes.io/component": "storage",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "worldbuilder-data",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "worldbuilder-data",
"namespace": "fc-worldbuilder"
},
"spec": {
"accessModes": [
"ReadWriteOnce"
],
"resources": {
"requests": {
"storage": "5Gi"
}
},
"storageClassName": "local-path",
"volumeMode": "Filesystem"
}
}

View File

@@ -59,7 +59,7 @@
},
{
"name": "KnowledgeFleetSearch__BaseUrl",
"value": "https://knowledge.iamworkin.lan"
"value": "http://knowledge-web.knowledge.svc.cluster.local"
},
{
"name": "KnowledgeFleetSearch__ApiKey",
@@ -82,7 +82,7 @@
}
}
],
"image": "localhost/fc-intranet-web:v20260617-sec5-intranet-1abdf90",
"image": "localhost/fc-intranet-web:v20260621-rum3-2c01daa",
"imagePullPolicy": "Never",
"securityContext": {
"allowPrivilegeEscalation": false,
@@ -166,7 +166,10 @@
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
"runAsUser": 1654,
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"terminationGracePeriodSeconds": 30,
"volumes": [

View File

@@ -22,8 +22,6 @@
]
}
],
"tls": {
"secretName": "intranet-tls"
}
}
}
"tls": {}
}
}

View File

@@ -0,0 +1,12 @@
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"labels": {
"pod-security.kubernetes.io/audit": "restricted",
"pod-security.kubernetes.io/enforce": "restricted",
"pod-security.kubernetes.io/warn": "restricted"
},
"name": "intranet"
}
}

View File

@@ -0,0 +1,15 @@
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "intranet-default-deny",
"namespace": "intranet"
},
"spec": {
"podSelector": {},
"policyTypes": [
"Ingress",
"Egress"
]
}
}

View File

@@ -0,0 +1,91 @@
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "intranet-web",
"namespace": "intranet"
},
"spec": {
"egress": [
{
"ports": [
{
"port": 53,
"protocol": "UDP"
},
{
"port": 53,
"protocol": "TCP"
}
],
"to": [
{
"namespaceSelector": {
"matchLabels": {
"kubernetes.io/metadata.name": "kube-system"
}
}
}
]
},
{
"ports": [
{
"port": 443,
"protocol": "TCP"
}
],
"to": [
{
"ipBlock": {
"cidr": "10.0.56.200/32"
}
}
]
},
{
"ports": [
{
"port": 11434,
"protocol": "TCP"
}
],
"to": [
{
"ipBlock": {
"cidr": "0.0.0.0/0"
}
}
]
}
],
"ingress": [
{
"from": [
{
"namespaceSelector": {
"matchLabels": {
"kubernetes.io/metadata.name": "traefik-system"
}
}
}
],
"ports": [
{
"port": 5300,
"protocol": "TCP"
}
]
}
],
"podSelector": {
"matchLabels": {
"app": "intranet-web"
}
},
"policyTypes": [
"Ingress",
"Egress"
]
}
}

View File

@@ -4,7 +4,8 @@
#
# GX10 adaptations vs OLD bluejay-infra/apps/irc/irc.yaml:
# - OnePasswordItem REPLACED by directly-copied Secrets (no OnePassword operator on GX10).
# irc-credentials is applied separately; cloak keys are injected from unrealircd-cloak-keys.
# irc-credentials and unrealircd-rpc-credentials are applied separately; cloak keys
# are injected from unrealircd-cloak-keys.
# - unrealircd image -> localhost/fc-unrealircd:6.1.9.1-arm64 (built on GX10 from
# DjLegolas/unrealircd-docker, alpine:3.19 base, UnrealIRCd 6.1.9.1 from source;
# djlegolas/unrealircd:6.1.9.1 on Docker Hub is amd64-ONLY -> would crashloop on arm64).
@@ -54,6 +55,20 @@ spec:
dnsNames:
- webirc.iamworkin.lan
---
# TLS Certificate for FlowerCore IRC Admin
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: irc-admin-tls
namespace: irc
spec:
secretName: irc-admin-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- irc-admin.iamworkin.lan
---
# The Lounge configuration
apiVersion: v1
kind: ConfigMap
@@ -587,6 +602,7 @@ data:
/* Credentials injected from copied K8s secret at pod startup */
include "modules.default.conf";
include "rpc.modules.default.conf";
include "help/help.conf";
include "operclass.default.conf";
include "snomasks.default.conf";
@@ -636,6 +652,19 @@ data:
port 6667;
}
listen {
ip *;
port 8600;
options { rpc; }
tls-options {
certificate "/app/conf/tls/server.cert.pem";
key "/app/conf/tls/server.key.pem";
options {
no-client-certificate;
}
}
}
listen {
ip *;
port 6697;
@@ -658,6 +687,49 @@ data:
class opers;
}
rpc-class flowercore-readonly {
permissions {
rpc { info; }
server { list; get; }
channel { list; get; }
user { list; get; }
stats { get; }
server_ban { list; get; }
log { subscribe; unsubscribe; }
}
}
rpc-user flowercorereadonly {
match {
ip 10.*;
ip 127.0.0.0/8;
}
password "__RPC_PASSWORD_HASH__";
rpc-class flowercore-readonly;
}
rpc-class flowercore-admin {
permissions {
rpc { info; set_issuer; }
server { list; get; }
channel { list; get; set_topic; kick; }
user { list; get; }
stats { get; }
server_ban { list; get; add; del; }
name_ban { list; get; add; del; }
log { subscribe; unsubscribe; }
}
}
rpc-user flowercoreadmin {
match {
ip 10.*;
ip 127.0.0.0/8;
}
password "__RPC_ADMIN_PASSWORD_HASH__";
rpc-class flowercore-admin;
}
drpass {
restart "__OPER_PASSWORD__";
die "__OPER_PASSWORD__";
@@ -1006,6 +1078,8 @@ spec:
metadata:
labels:
app: unrealircd
annotations:
flowercore.io/config-revision: "irc-admin-rpc-auth-20260620-r3"
spec:
initContainers:
- name: inject-credentials
@@ -1014,11 +1088,15 @@ spec:
args:
- |
OPER_PW=$(cat /secrets/password)
RPC_PW_HASH=$(cat /rpc-secrets/passwordHash)
RPC_ADMIN_PW_HASH=$(cat /rpc-secrets/adminPasswordHash)
LINK_PW=$(cat /secrets/Link-Password)
CLOAK_KEY_1=$(cat /cloak-secrets/cloak-key-1)
CLOAK_KEY_2=$(cat /cloak-secrets/cloak-key-2)
CLOAK_KEY_3=$(cat /cloak-secrets/cloak-key-3)
sed -e "s|__OPER_PASSWORD__|${OPER_PW}|g" \
-e "s|__RPC_PASSWORD_HASH__|${RPC_PW_HASH}|g" \
-e "s|__RPC_ADMIN_PASSWORD_HASH__|${RPC_ADMIN_PW_HASH}|g" \
-e "s|__LINK_PASSWORD__|${LINK_PW}|g" \
-e "s|__CLOAK_KEY_1__|${CLOAK_KEY_1}|g" \
-e "s|__CLOAK_KEY_2__|${CLOAK_KEY_2}|g" \
@@ -1029,6 +1107,9 @@ spec:
- name: irc-credentials
mountPath: /secrets
readOnly: true
- name: unrealircd-rpc-credentials
mountPath: /rpc-secrets
readOnly: true
- name: unrealircd-cloak-keys
mountPath: /cloak-secrets
readOnly: true
@@ -1067,6 +1148,8 @@ spec:
name: irc-tls
- containerPort: 8067
name: services-link
- containerPort: 8600
name: rpc
volumeMounts:
- name: injected-config
mountPath: /app/conf/unrealircd.conf
@@ -1090,6 +1173,9 @@ spec:
- name: irc-credentials
secret:
secretName: irc-credentials
- name: unrealircd-rpc-credentials
secret:
secretName: unrealircd-rpc-credentials
- name: unrealircd-cloak-keys
secret:
secretName: unrealircd-cloak-keys
@@ -1263,7 +1349,7 @@ spec:
configMap:
name: thelounge-flowercore-theme
---
# UnrealIRCd internal Service (anope uplink 8067 + in-cluster 6667 for thelounge)
# UnrealIRCd internal Service (anope uplink 8067 + in-cluster 6667 for thelounge + in-cluster JSON-RPC)
apiVersion: v1
kind: Service
metadata:
@@ -1282,6 +1368,9 @@ spec:
- port: 8067
targetPort: 8067
name: services-link
- port: 8600
targetPort: 8600
name: rpc
---
# UnrealIRCd external LoadBalancer — exposes IRC TCP 6667/6697 on the GX10 PROD MetalLB pool.
# (Replaces OLD's Traefik irc/irctls entryPoints; GX10 Traefik has no such entryPoints.)
@@ -1335,6 +1424,209 @@ spec:
targetPort: 9000
name: http
---
# FlowerCore IRC management web app data
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: irc-web-data
namespace: irc
labels:
app: irc-web
app.kubernetes.io/name: irc-web
app.kubernetes.io/part-of: flowercore
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
# FlowerCore IRC management web app.
apiVersion: apps/v1
kind: Deployment
metadata:
name: irc-web
namespace: irc
labels:
app: irc-web
app.kubernetes.io/name: irc-web
app.kubernetes.io/part-of: flowercore
spec:
replicas: 1
revisionHistoryLimit: 3
strategy:
type: Recreate
selector:
matchLabels:
app: irc-web
template:
metadata:
labels:
app: irc-web
app.kubernetes.io/name: irc-web
app.kubernetes.io/part-of: flowercore
annotations:
fc.flowercore.io/healthz-anon: "true"
fc.flowercore.io/probe-path: "/healthz"
prometheus.io/scrape: "true"
prometheus.io/port: "5080"
prometheus.io/path: "/metrics/prometheus"
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
fsGroup: 1654
fsGroupChangePolicy: OnRootMismatch
containers:
- name: web
image: localhost/fc-irc-web:v20260621-irc-services-admin-3b8fbaf
imagePullPolicy: Never
ports:
- containerPort: 5080
name: http
env:
- name: ASPNETCORE_URLS
value: "http://+:5080"
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
value: "false"
- name: HOME
value: "/data"
- name: FlowerCore__IRC__BaseUrl
value: "http://irc-web.irc.svc.cluster.local"
- name: FlowerCore__IRC__Persistence__Provider
value: "Sqlite"
- name: FlowerCore__IRC__Persistence__ConnectionString
value: "Data Source=/data/irc-web.db"
- name: FlowerCore__IRC__Persistence__DefaultTenantId
value: "default"
- name: FlowerCore__IRC__ServerManagement__NetworkName
value: "BlueJayIRC"
- name: FlowerCore__IRC__ServerManagement__Host
value: "irc.iamworkin.lan"
- name: FlowerCore__IRC__ServerManagement__TlsPort
value: "6697"
- name: FlowerCore__IRC__ServerManagement__RpcEnabled
value: "true"
- name: FlowerCore__IRC__ServerManagement__RpcBaseUrl
value: "https://unrealircd.irc.svc.cluster.local:8600"
- name: FlowerCore__IRC__ServerManagement__RpcPath
value: "/api"
- name: FlowerCore__IRC__ServerManagement__RpcUsername
value: "flowercoreadmin"
- name: FlowerCore__IRC__ServerManagement__RpcPassword
valueFrom:
secretKeyRef:
name: unrealircd-rpc-credentials
key: adminPassword
- name: FlowerCore__IRC__ServerManagement__RpcAllowInvalidServerCertificate
value: "true"
- name: FlowerCore__IRC__ServerManagement__PreferRpcReadModel
value: "true"
- name: FlowerCore__IRC__ServerManagement__ServicesControlEnabled
value: "false"
- name: FlowerCore__Mcp__ServiceName
value: "fc-irc"
- name: FlowerCore__Mcp__RoutePath
value: "/mcp"
- name: FlowerCore__Mcp__RequireAuthorization
value: "true"
- name: FlowerCore__Auth__Enabled
value: "true"
- name: FlowerCore__Auth__Oidc__Enabled
value: "true"
- name: FlowerCore__Auth__Oidc__Audience
value: "irc"
- name: FlowerCore__Auth__Oidc__Authority
valueFrom:
secretKeyRef:
name: irc-oidc-client
key: issuer_url
optional: true
- name: FlowerCore__Auth__Oidc__ClientId
valueFrom:
secretKeyRef:
name: irc-oidc-client
key: client_id
optional: true
- name: FlowerCore__Auth__Oidc__ClientSecret
valueFrom:
secretKeyRef:
name: irc-oidc-client
key: client_secret
optional: true
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 384Mi
securityContext:
runAsNonRoot: true
runAsUser: 1654
runAsGroup: 1654
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: data
mountPath: /data
- name: tmp
mountPath: /tmp
- name: logs
mountPath: /app/logs
startupProbe:
httpGet:
path: /healthz
port: 5080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30
readinessProbe:
httpGet:
path: /healthz
port: 5080
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 5080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: data
persistentVolumeClaim:
claimName: irc-web-data
- name: tmp
emptyDir: {}
- name: logs
emptyDir: {}
---
# FlowerCore IRC management web app Service
apiVersion: v1
kind: Service
metadata:
name: irc-web
namespace: irc
labels:
app: irc-web
app.kubernetes.io/name: irc-web
app.kubernetes.io/part-of: flowercore
spec:
type: ClusterIP
selector:
app: irc-web
ports:
- name: http
port: 80
targetPort: 5080
---
# Traefik IngressRoute - The Lounge web IRC (webirc.iamworkin.lan)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
@@ -1352,3 +1644,21 @@ spec:
port: 9000
tls:
secretName: webirc-tls
---
# Traefik IngressRoute - FlowerCore IRC Admin (irc-admin.iamworkin.lan)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: irc-admin
namespace: irc
spec:
entryPoints:
- websecure
routes:
- match: Host(`irc-admin.iamworkin.lan`)
kind: Rule
services:
- name: irc-web
port: 80
tls:
secretName: irc-admin-tls

View File

@@ -147,7 +147,7 @@
"value": "/data/vector-stores/corpus-cache"
}
],
"image": "localhost/fc-knowledge-web:gx10-v1",
"image": "localhost/fc-knowledge-web:v20260621-rum2-ace701d-b87cbf4",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
@@ -233,11 +233,14 @@
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsNonRoot": true
},
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsNonRoot": true,
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{

View File

@@ -22,8 +22,6 @@
]
}
],
"tls": {
"secretName": "knowledge-tls"
}
}
}
"tls": {}
}
}

View File

@@ -0,0 +1,20 @@
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "knowledge",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system",
"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"
},
"name": "knowledge"
}
}

View File

@@ -0,0 +1,15 @@
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "knowledge-default-deny",
"namespace": "knowledge"
},
"spec": {
"podSelector": {},
"policyTypes": [
"Ingress",
"Egress"
]
}
}

View File

@@ -0,0 +1,132 @@
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "knowledge-web",
"namespace": "knowledge"
},
"spec": {
"podSelector": {
"matchLabels": {
"app": "knowledge-web"
}
},
"policyTypes": [
"Ingress",
"Egress"
],
"ingress": [
{
"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"
}
]
},
{
"from": [
{
"namespaceSelector": {
"matchLabels": {
"kubernetes.io/metadata.name": "fc-gateway"
}
}
},
{
"namespaceSelector": {
"matchLabels": {
"kubernetes.io/metadata.name": "intranet"
}
}
}
],
"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": [
{
"ipBlock": {
"cidr": "0.0.0.0/0"
}
}
],
"ports": [
{
"port": 11434,
"protocol": "TCP"
}
]
},
{
"to": [
{
"ipBlock": {
"cidr": "0.0.0.0/0"
}
}
],
"ports": [
{
"port": 443,
"protocol": "TCP"
}
]
}
]
}
}

View File

@@ -99,7 +99,7 @@
"value": "piper"
}
],
"image": "localhost/fc-telephony-web:gx10-v2",
"image": "localhost/fc-telephony-web:v20260620-telephony-vr-af97700",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,

View File

@@ -10,8 +10,9 @@
# Phase 1 production uses a Longhorn RWO PVC at /data/devicemgmt.db. The
# 1Password runtime item stays mounted through env for future MySQL/API-key
# cutover, but MySQL is not required for this first product-host rollout.
# Image v20260613-g2-66a43c1 is built from FlowerCore.DeviceManagement master
# 66a43c1, carrying edge enrollment network completion and SQLite-safe trust-bundle smoke coverage.
# Image v20260618-prune-18c7449-livebase is derived from the 2026-06-17 AN-13
# live base with the Mac fleet SQLite snapshot-prune hotfix from
# FlowerCore.DeviceManagement PR #49.
---
apiVersion: v1
kind: PersistentVolumeClaim
@@ -83,7 +84,7 @@ spec:
fsGroupChangePolicy: OnRootMismatch
containers:
- name: web
image: localhost/fc-devicemgmt-web:v20260614-regroup-c5b8f82
image: localhost/fc-devicemgmt-web:v20260618-prune-18c7449-livebase
imagePullPolicy: Never
ports:
- name: http

View File

@@ -43,5 +43,6 @@ shared origin cert must exist in every namespace that serves a
```powershell
kubectl.exe --kubeconfig C:\Users\AndrewStoltz\.kube\rke2.yaml -n argocd get application infra-fc-updater
kubectl.exe --kubeconfig C:\Users\AndrewStoltz\.kube\rke2.yaml -n fc-updater get deploy,svc,ingressroute,certificate,pvc
curl.exe -sk https://update.flowercore.io/api/v1/manifests/_schema
curl.exe -sk https://update.flowercore.io/
curl.exe -sk -o NUL -w "%{http_code}`n" https://update.flowercore.io/login
```

View File

@@ -61,7 +61,7 @@ spec:
nodeName: rke2-server
containers:
- name: web
image: localhost/fc-updater-web:v20260614-regroup-bdf4a4a
image: localhost/fc-updater-web:v20260620-updater-sec5-f20976e
imagePullPolicy: Never
ports:
- containerPort: 8080
@@ -92,6 +92,22 @@ spec:
value: Faith AI Mike Edition
- name: FlowerCore__Updater__PublicShares__Links__0__Description
value: Private release link for Mike's Faith AI bundle.
- name: FlowerCore__Updater__PublicShares__Links__1__RoutePrefix
value: kiosk-bundle
- name: FlowerCore__Updater__PublicShares__Links__1__Code
value: b22f04a5ff39
- name: FlowerCore__Updater__PublicShares__Links__1__AppId
value: flowercore.kiosk
- name: FlowerCore__Updater__PublicShares__Links__1__Channel
value: stable
- name: FlowerCore__Updater__PublicShares__Links__1__RuntimeId
value: win-x64
- name: FlowerCore__Updater__PublicShares__Links__1__DisplayName
value: FlowerCore Kiosk
- name: FlowerCore__Updater__PublicShares__Links__1__Headline
value: FlowerCore Kiosk
- name: FlowerCore__Updater__PublicShares__Links__1__Description
value: Private release link for the FlowerCore Kiosk Windows installer.
- name: FlowerCore__Audit__Sinks__Loki__Enabled
value: "false"
- name: FlowerCore__Updater__Auth__Bootstrap__Enabled
@@ -266,7 +282,7 @@ spec:
entryPoints:
- websecure
routes:
- match: (Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))
- match: (Host(`update.flowercore.io`) || Host(`updates.flowercore.io`)) && (Method(`GET`) || Method(`HEAD`))
kind: Rule
services:
- name: updatecenter-web

View File

@@ -8,7 +8,8 @@ auto-deploy them there. Once ArgoCD is stood up on the GX10, a GX10-only
ApplicationSet (`apps-gx10/*`) will own these.
- `step-ca-acme.yaml` — cert-manager ClusterIssuer (ACME → noc1 step-ca, in-spec caBundle). APPLIED + Ready.
- `traefik-helmchart.yaml` — Traefik v3.6.10 (chart 39.0.5) via the RKE2 HelmChart CRD, LoadBalancer VIP 10.0.57.202 (prod-pool; temp parallel-run VIP — canonical .200 reclaimed at cutover). APPLIED.
- `traefik-helmchart.yaml` — Traefik v3.6.10 (chart 39.0.5), live as a Helm release in `traefik-system`, LoadBalancer VIP `10.0.56.200` from the active `bluejay-pool` (`10.0.56.200-10.0.56.220`). APPLIED.
- `gitea-ssh-service.yaml` — Gitea SSH LoadBalancer service on `10.0.57.206:22` with `externalTrafficPolicy: Local`; HTTPS Gitea remains behind the Traefik VIP at `10.0.56.200`. APPLIED.
cert-manager v1.17.2 was installed separately (upstream static manifest). See
`docs/ai-agents/gx10-migration-continuation-2026-06-14.md` + memory

View File

@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: gitea-ssh
namespace: gitea
annotations:
metallb.io/loadBalancerIPs: 10.0.57.206
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: gitea
ports:
- name: ssh
port: 22
protocol: TCP
targetPort: 2222

View File

@@ -10,72 +10,73 @@ spec:
targetNamespace: traefik-system
createNamespace: true
valuesContent: |
deployment:
replicas: 1
additionalArguments:
- "--api.dashboard=true"
- "--log.level=INFO"
- "--providers.kubernetescrd"
- "--providers.kubernetesingress"
- "--providers.kubernetescrd.allowEmptyServices=true"
- "--providers.kubernetesingress.allowEmptyServices=true"
- "--providers.kubernetesingress.ingressendpoint.publishedservice=traefik-system/traefik"
- --api.dashboard=true
- --log.level=INFO
- --providers.kubernetescrd
- --providers.kubernetesingress
deployment:
replicas: 2
service:
type: LoadBalancer
spec:
externalTrafficPolicy: Cluster
loadBalancerIP: 10.0.56.200
ports:
irc:
expose:
default: true
exposedPort: 6667
port: 6667
protocol: TCP
irctls:
expose:
default: true
exposedPort: 6697
port: 6697
protocol: TCP
traefik:
expose:
default: false
exposedPort: 8080
port: 8080
protocol: TCP
web:
exposedPort: 80
port: 8000
protocol: TCP
websecure:
exposedPort: 443
port: 8443
protocol: TCP
rbac:
enabled: true
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
operator: Exists
ingressClass:
enabled: true
isDefaultClass: false
providers:
kubernetesCRD:
enabled: true
allowEmptyServices: true
kubernetesIngress:
enabled: true
allowEmptyServices: true
publishedService:
enabled: true
ingressRoute:
dashboard:
enabled: false
rbac:
enabled: true
service:
type: LoadBalancer
annotations:
metallb.io/loadBalancerIPs: "10.0.57.202"
metallb.io/address-pool: "prod-pool"
ports:
web:
port: 8000
exposedPort: 80
protocol: TCP
websecure:
port: 8443
exposedPort: 443
protocol: TCP
tls:
enabled: true
irc:
port: 6667
exposedPort: 6667
protocol: TCP
expose:
default: true
irctls:
port: 6697
exposedPort: 6697
protocol: TCP
expose:
default: true
traefik:
port: 8080
exposedPort: 8080
protocol: TCP
expose:
default: false
metrics:
port: 9100
exposedPort: 9100
protocol: TCP
expose:
default: false
metrics:
prometheus:
entryPoint: metrics
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
logs:
general:
level: INFO

View File

@@ -16,6 +16,8 @@ public sealed class FleetManifestLintTests
{
"brochure.flowercore.io",
"dist.flowercore.io",
"update.flowercore.io",
"updates.flowercore.io",
};
// Hosts that allow a tightly bounded write surface in addition to GET/HEAD.
@@ -247,6 +249,22 @@ public sealed class FleetManifestLintTests
violations.Should().BeEmpty();
}
[Fact]
public void Gx10PublicLoadBalancers_MustPreserveClientSourceIp()
{
var traefikPath = Path.Combine(Inventory.BluejayRoot, "gx10", "platform", "traefik-helmchart.yaml");
var traefik = File.ReadAllText(traefikPath);
traefik.Should().Contain("loadBalancerIP: 10.0.56.200");
traefik.Should().Contain("externalTrafficPolicy: Cluster");
var giteaPath = Path.Combine(Inventory.BluejayRoot, "gx10", "platform", "gitea-ssh-service.yaml");
var gitea = File.ReadAllText(giteaPath);
gitea.Should().Contain("metallb.io/loadBalancerIPs: 10.0.57.206");
gitea.Should().Contain("externalTrafficPolicy: Local");
}
[Fact]
public void ApiKeyProtectedDeployments_MustUseTcpSocketHealthProbes()
{
@@ -981,6 +999,32 @@ public sealed class FleetManifestLintTests
gatewayManifest.Should().Contain("port: 5400");
}
[Fact]
public void Gx10DeviceManagementWriteApis_RequireRuntimeBackedOperatorAuth()
{
var web = Gx10DeploymentContainer("fc-devicemgmt", "deployment-fc-devicemgmt-web.json");
JsonEnvValue(web, "FlowerCore__Auth__Enabled").Should().Be("true");
JsonEnvSecretName(web, "Auth__ApiKey").Should().Be("fc-devicemgmt-runtime");
JsonEnvSecretKey(web, "Auth__ApiKey").Should().Be("DEVICE_MANAGEMENT_OPERATOR_API_KEY");
JsonEnvSecretOptional(web, "Auth__ApiKey").Should().BeNull();
JsonEnvSecretName(web, "FlowerCore__Auth__ApiKey").Should().Be("fc-devicemgmt-runtime");
JsonEnvSecretKey(web, "FlowerCore__Auth__ApiKey").Should().Be("DEVICE_MANAGEMENT_OPERATOR_API_KEY");
JsonEnvSecretOptional(web, "FlowerCore__Auth__ApiKey").Should().BeNull();
JsonEnvSecretName(web, "Auth__AdminApiKey").Should().Be("fc-devicemgmt-runtime");
JsonEnvSecretKey(web, "Auth__AdminApiKey").Should().Be("DEVICE_MANAGEMENT_ADMIN_API_KEY");
JsonEnvSecretOptional(web, "Auth__AdminApiKey").Should().BeNull();
JsonEnvSecretName(web, "FlowerCore__Auth__AdminApiKey").Should().Be("fc-devicemgmt-runtime");
JsonEnvSecretKey(web, "FlowerCore__Auth__AdminApiKey").Should().Be("DEVICE_MANAGEMENT_ADMIN_API_KEY");
JsonEnvSecretOptional(web, "FlowerCore__Auth__AdminApiKey").Should().BeNull();
JsonEnvSecretName(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem").Should().Be("fc-devicemgmt-runtime");
JsonEnvSecretKey(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem").Should().Be("DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM");
JsonEnvSecretOptional(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem").Should().BeTrue();
JsonEnvSecretName(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem").Should().Be("fc-devicemgmt-runtime");
JsonEnvSecretKey(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem").Should().Be("DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM");
JsonEnvSecretOptional(web, "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem").Should().BeTrue();
}
[Fact]
public void Gx10PhpTenantRoutes_HaveEdgeControlSubstrate()
{
@@ -1108,9 +1152,10 @@ public sealed class FleetManifestLintTests
servicePort.GetProperty("targetPort").GetInt32().Should().Be(8080);
using var ingressRoute = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "ingressroute-andrew-web.json")));
var serviceRef = ingressRoute.RootElement
var route = ingressRoute.RootElement
.GetProperty("spec")
.GetProperty("routes")[0]
.GetProperty("routes")[0];
var serviceRef = route
.GetProperty("services")
.EnumerateArray()
.Should()
@@ -1118,6 +1163,70 @@ public sealed class FleetManifestLintTests
.Subject;
serviceRef.GetProperty("name").GetString().Should().Be("andrew-web-waf");
serviceRef.GetProperty("port").GetInt32().Should().Be(8080);
route.GetProperty("middlewares")
.EnumerateArray()
.Select(item => item.GetProperty("name").GetString())
.Should()
.Equal("andrew-tenant-rate-limit", "andrew-tenant-secure-headers");
var adminRoute = ingressRoute.RootElement
.GetProperty("spec")
.GetProperty("routes")
.EnumerateArray()
.Single(route => route.GetProperty("match").GetString()!.Contains("PathPrefix(`/admin-allowlist-proof`)", StringComparison.Ordinal));
adminRoute.GetProperty("priority").GetInt32().Should().Be(300);
adminRoute.GetProperty("services").EnumerateArray().Should().ContainSingle().Subject
.GetProperty("name").GetString().Should().Be("andrew-web-waf");
adminRoute.GetProperty("middlewares")
.EnumerateArray()
.Select(item => item.GetProperty("name").GetString())
.Should()
.Equal("andrew-admin-ip-allowlist", "andrew-tenant-rate-limit", "andrew-tenant-secure-headers");
using var rateLimit = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "middleware-andrew-tenant-rate-limit.json")));
rateLimit.RootElement.GetProperty("spec").GetProperty("rateLimit").GetProperty("average").GetInt32().Should().Be(120);
using var allowlist = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "middleware-andrew-admin-ip-allowlist.json")));
allowlist.RootElement.GetProperty("kind").GetString().Should().Be("Middleware");
allowlist.RootElement.GetProperty("spec").GetProperty("ipAllowList").GetProperty("sourceRange")
.EnumerateArray()
.Select(item => item.GetString())
.Should()
.Equal("10.0.56.14/32");
using var nginxConfig = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "configmap-andrew-web-nginx-conf.json")));
var nginx = nginxConfig.RootElement.GetProperty("data").GetProperty("default.conf").GetString();
nginx.Should().Contain("location = /lamp-canary/index.php");
nginx.Should().Contain("location = /lamp-canary/wp-login.php");
nginx.Should().Contain("location = /lamp-canary/mediawiki/index.php");
nginx.Should().Contain("location = /admin-allowlist-proof");
using var webDeployment = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "deployment-andrew-web.json")));
webDeployment.RootElement.GetProperty("spec")
.GetProperty("template")
.GetProperty("metadata")
.GetProperty("annotations")
.GetProperty("flowercore.io/config-revision")
.GetString()
.Should()
.Be("whc4-lamp-allowlist-20260618");
using var headers = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "middleware-andrew-tenant-secure-headers.json")));
var headerSpec = headers.RootElement.GetProperty("spec").GetProperty("headers");
headerSpec.GetProperty("contentTypeNosniff").GetBoolean().Should().BeTrue();
headerSpec.GetProperty("stsSeconds").GetInt32().Should().Be(31536000);
using var tlsOption = JsonDocument.Parse(File.ReadAllText(Path.Combine(appRoot, "tlsoption-andrew-tenant-tls13.json")));
tlsOption.RootElement.GetProperty("spec").GetProperty("minVersion").GetString().Should().Be("VersionTLS13");
ingressRoute.RootElement
.GetProperty("spec")
.GetProperty("tls")
.GetProperty("options")
.GetProperty("name")
.GetString()
.Should()
.Be("andrew-tenant-tls13");
}
[Fact]
@@ -1221,6 +1330,39 @@ public sealed class FleetManifestLintTests
match.Should().NotContain("Method(`POST`)");
}
[Fact]
public void UpdateCenterPublicIngress_KeepsDeliveryOnlyGetHeadMethodAllowlist()
{
var publicIngress = AppDocuments("fc-updater")
.Single(document => document.Kind == "IngressRoute" && document.Name == "updatecenter-web-public");
var route = publicIngress.MappingSequence("spec", "routes").Should().ContainSingle().Subject;
var match = ManifestNodeExtensions.Scalar(route, "match");
match.Should().Contain("Host(`update.flowercore.io`)");
match.Should().Contain("Host(`updates.flowercore.io`)");
match.Should().Contain("Method(`GET`)");
match.Should().Contain("Method(`HEAD`)");
match.Should().NotContain("Method(`POST`)");
match.Should().NotContain("Method(`OPTIONS`)");
}
[Fact]
public void Gx10UpdateCenterPublicIngress_StaysGetHeadOnlyAndUsesContainmentImage()
{
var appRoot = Path.Combine(Inventory.BluejayRoot, "apps-gx10", "fc-updater");
var publicRoute = File.ReadAllText(Path.Combine(appRoot, "ingressroute-updatecenter-web-public-gx10.json"));
var deployment = File.ReadAllText(Path.Combine(appRoot, "deployment-updatecenter-web.json"));
publicRoute.Should().Contain("Host(`update.flowercore.io`)");
publicRoute.Should().Contain("Host(`updates.flowercore.io`)");
publicRoute.Should().Contain("Method(`GET`)");
publicRoute.Should().Contain("Method(`HEAD`)");
publicRoute.Should().NotContain("Method(`POST`)");
publicRoute.Should().NotContain("Method(`OPTIONS`)");
deployment.Should().Contain("localhost/fc-updater-web:v");
deployment.Should().NotContain("localhost/fc-updater-web:v20260614-regroup-bdf4a4a");
}
[Fact]
public void DnsAndMediaIngressRoutes_MatchLiveInternalHosts()
{
@@ -1332,9 +1474,13 @@ public sealed class FleetManifestLintTests
private static bool? JsonEnvSecretOptional(JsonElement container, string name)
{
return JsonEnvMapping(container, name) is { } env
? env.GetProperty("valueFrom").GetProperty("secretKeyRef").GetProperty("optional").GetBoolean()
: null;
if (JsonEnvMapping(container, name) is not { } env)
{
return null;
}
var secretKeyRef = env.GetProperty("valueFrom").GetProperty("secretKeyRef");
return secretKeyRef.TryGetProperty("optional", out var optional) ? optional.GetBoolean() : null;
}
private static string? JsonEnvValue(JsonElement container, string name)

View File

@@ -0,0 +1,208 @@
using FluentAssertions;
using Xunit;
using YamlDotNet.RepresentationModel;
namespace BluejayInfraLint.Tests;
[Trait("Category", "Unit")]
public sealed class Gx10AppleMdmNanohubTests
{
private static readonly string Root = FindRepoRoot();
private static readonly string AppRoot = Path.Combine(Root, "apps-gx10", "fc-apple-mdm");
private static readonly IReadOnlyList<YamlMappingNode> Documents = LoadDocuments();
[Fact]
public void Manifest_DeclaresLockedDownNanoHubRuntime()
{
Documents.Should().Contain(document => Is(document, "Namespace", "fc-apple-mdm"));
Documents.Should().Contain(document => Is(document, "ConfigMap", "fc-apple-mdm-root-ca"));
Documents.Should().Contain(document => Is(document, "Service", "fc-apple-mdm"));
Documents.Should().Contain(document => Is(document, "Service", "fc-apple-mdm-scep"));
Documents.Should().Contain(document => Is(document, "EndpointSlice", "fc-apple-mdm-scep-noc1"));
Documents.Should().Contain(document => Is(document, "NetworkPolicy", "fc-apple-mdm-netpol"));
Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "Secret");
Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "OnePasswordItem");
var pvc = Single("PersistentVolumeClaim", "fc-apple-mdm-data");
pvc.Scalar("spec", "storageClassName").Should().Be("local-path");
pvc.Scalar("spec", "resources", "requests", "storage").Should().Be("2Gi");
var deployment = Single("Deployment", "fc-apple-mdm");
deployment.Scalar("spec", "strategy", "type").Should().Be("Recreate");
deployment.Scalar("spec", "template", "metadata", "annotations", "fc.flowercore.io/probe-path").Should().Be("/version");
deployment.Scalar("spec", "template", "metadata", "annotations", "flowercore.io/root-ca-sha256")
.Should()
.Be("a9120c88fa3ec735d790aa4cfeb61ac2946730338969015bebaccc08fe10535e");
var maybePodSpec = deployment.Mapping("spec", "template", "spec");
maybePodSpec.Should().NotBeNull();
var podSpec = maybePodSpec!;
podSpec.Scalar("enableServiceLinks").Should().Be("false");
podSpec.Scalar("securityContext", "runAsUser").Should().Be("1654");
podSpec.Scalar("securityContext", "runAsNonRoot").Should().Be("true");
var container = podSpec.MappingSequence("containers").Should().ContainSingle().Subject;
container.Scalar("name").Should().Be("nanohub");
container.Scalar("image").Should().Be("localhost/fc-apple-mdm-nanohub:v0.2.0-20260617");
container.Scalar("imagePullPolicy").Should().Be("Never");
container.Scalar("securityContext", "readOnlyRootFilesystem").Should().Be("true");
container.Scalar("securityContext", "allowPrivilegeEscalation").Should().Be("false");
EnvValue(container, "NANOHUB_LISTEN").Should().Be(":9004");
EnvValue(container, "NANOHUB_STORAGE").Should().Be("file");
EnvValue(container, "NANOHUB_STORAGE_DSN").Should().Be("/var/lib/nanohub/db");
EnvValue(container, "NANOHUB_CHECKIN").Should().Be("true");
EnvValue(container, "NANOHUB_CA").Should().Be("/etc/nanohub/ca/root_ca.crt");
EnvSecretName(container, "NANOHUB_API_KEY").Should().Be("fc-apple-mdm-runtime");
EnvSecretKey(container, "NANOHUB_API_KEY").Should().Be("NANOHUB_API_KEY");
EnvSecretName(container, "NANOHUB_WEBHOOK_URL").Should().Be("fc-apple-mdm-runtime");
EnvSecretKey(container, "NANOHUB_WEBHOOK_URL").Should().Be("NANOHUB_WEBHOOK_URL");
EnvSecretOptional(container, "NANOHUB_WEBHOOK_URL").Should().Be("true");
VolumeMount(container, "data").Scalar("mountPath").Should().Be("/var/lib/nanohub");
VolumeMount(container, "root-ca").Scalar("mountPath").Should().Be("/etc/nanohub/ca");
VolumeMount(container, "root-ca").Scalar("readOnly").Should().Be("true");
ProbePath(container, "startupProbe").Should().Be("/version");
ProbePath(container, "readinessProbe").Should().Be("/version");
container.Scalar("livenessProbe", "tcpSocket", "port").Should().Be("9004");
}
[Fact]
public void Manifest_ExposesOnlyMdmCheckinVersionAndScepPaths()
{
var certificate = Single("Certificate", "fc-apple-mdm-tls");
certificate.Scalar("spec", "issuerRef", "name").Should().Be("step-ca-acme");
certificate.Scalar("spec", "issuerRef", "kind").Should().Be("ClusterIssuer");
certificate.ScalarSequence("spec", "dnsNames").Should().ContainSingle("mdm.iamworkin.lan");
var scepService = Single("Service", "fc-apple-mdm-scep");
scepService.Scalar("spec", "type").Should().Be("ClusterIP");
var scepServicePort = scepService.MappingSequence("spec", "ports").Should().ContainSingle().Subject;
scepServicePort.Scalar("name").Should().Be("http");
scepServicePort.Scalar("port").Should().Be("80");
scepServicePort.Scalar("targetPort").Should().Be("9080");
var scepEndpointSlice = Single("EndpointSlice", "fc-apple-mdm-scep-noc1");
scepEndpointSlice.Scalar("addressType").Should().Be("IPv4");
scepEndpointSlice.Scalar("metadata", "labels", "kubernetes.io/service-name").Should().Be("fc-apple-mdm-scep");
var scepEndpoint = scepEndpointSlice.MappingSequence("endpoints").Should().ContainSingle().Subject;
scepEndpoint.ScalarSequence("addresses").Should().ContainSingle("10.0.56.10");
var scepEndpointPort = scepEndpointSlice.MappingSequence("ports").Should().ContainSingle().Subject;
scepEndpointPort.Scalar("name").Should().Be("http");
scepEndpointPort.Scalar("port").Should().Be("9080");
var ingress = Single("IngressRoute", "fc-apple-mdm");
var routes = ingress.MappingSequence("spec", "routes");
routes.Should().HaveCount(2);
var scepRoute = routes.Single(route => route.Scalar("match")?.Contains("PathPrefix(`/scep`)") == true);
var nanohubRoute = routes.Single(route => route.Scalar("match")?.Contains("PathPrefix(`/mdm`)") == true);
var match = nanohubRoute.Scalar("match");
match.Should().Contain("Host(`mdm.iamworkin.lan`)");
match.Should().Contain("PathPrefix(`/mdm`)");
match.Should().Contain("PathPrefix(`/checkin`)");
match.Should().Contain("PathPrefix(`/version`)");
match.Should().NotContain("/api/v1");
match.Should().NotContain("PathPrefix(`/api`)");
scepRoute.Scalar("match").Should().Contain("Host(`mdm.iamworkin.lan`)");
scepRoute.Scalar("match").Should().Contain("PathPrefix(`/scep`)");
var scepRouteService = scepRoute.MappingSequence("services").Should().ContainSingle().Subject;
scepRouteService.Scalar("name").Should().Be("fc-apple-mdm-scep");
scepRouteService.Scalar("port").Should().Be("80");
}
[Fact]
public void Readme_DocumentsSecretImportAndSupportBoundary()
{
var readme = File.ReadAllText(Path.Combine(AppRoot, "README.md"));
readme.Should().Contain("FlowerCore Apple MDM Runtime");
readme.Should().Contain("Secret/fc-apple-mdm-runtime");
readme.Should().Contain("imagePullPolicy: Never");
readme.Should().Contain("10.0.56.200");
readme.Should().Contain("https://mdm.iamworkin.lan/scep/apple-mdm-scep");
readme.Should().Contain("Smallstep SCEP requires an RSA intermediate");
readme.Should().Contain("does not create an APNs MDM push certificate");
readme.Should().Contain("managed Wi-Fi payload");
}
private static YamlMappingNode Single(string kind, string name)
{
return Documents.Single(document => Is(document, kind, name));
}
private static bool Is(YamlMappingNode document, string kind, string name)
{
return document.Scalar("kind") == kind
&& document.Scalar("metadata", "name") == name;
}
private static string? EnvValue(YamlMappingNode container, string name)
{
return EnvMapping(container, name)?.Scalar("value");
}
private static string? EnvSecretName(YamlMappingNode container, string name)
{
return EnvMapping(container, name)?.Scalar("valueFrom", "secretKeyRef", "name");
}
private static string? EnvSecretKey(YamlMappingNode container, string name)
{
return EnvMapping(container, name)?.Scalar("valueFrom", "secretKeyRef", "key");
}
private static string? EnvSecretOptional(YamlMappingNode container, string name)
{
return EnvMapping(container, name)?.Scalar("valueFrom", "secretKeyRef", "optional");
}
private static string? ProbePath(YamlMappingNode container, string probeKey)
{
return container.Scalar(probeKey, "httpGet", "path");
}
private static YamlMappingNode VolumeMount(YamlMappingNode container, string name)
{
return container.MappingSequence("volumeMounts")
.Single(mount => mount.Scalar("name") == name);
}
private static YamlMappingNode? EnvMapping(YamlMappingNode container, string name)
{
return container.MappingSequence("env")
.SingleOrDefault(env => env.Scalar("name") == name);
}
private static IReadOnlyList<YamlMappingNode> LoadDocuments()
{
var stream = new YamlStream();
using var reader = File.OpenText(Path.Combine(AppRoot, "fc-apple-mdm.yaml"));
stream.Load(reader);
return stream.Documents
.Select(document => document.RootNode)
.OfType<YamlMappingNode>()
.Where(mapping => !string.IsNullOrWhiteSpace(mapping.Scalar("kind")))
.ToList();
}
private static string FindRepoRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (Directory.Exists(Path.Combine(current.FullName, "apps-gx10"))
&& Directory.Exists(Path.Combine(current.FullName, "tests"))
&& File.Exists(Path.Combine(current.FullName, "README.md")))
{
return current.FullName;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Could not find bluejay-infra root.");
}
}

View File

@@ -1,6 +1,12 @@
package bluejayinfra.public_method_allowlist
public_hosts := {"brochure.flowercore.io", "dist.flowercore.io", "dns.iamworkin.lan"}
public_hosts := {
"brochure.flowercore.io",
"dist.flowercore.io",
"dns.iamworkin.lan",
"update.flowercore.io",
"updates.flowercore.io",
}
deny[msg] {
input.kind == "IngressRoute"

View File

@@ -9,8 +9,6 @@ package bluejayinfra.public_readwrite_allowlist
public_readwrite_hosts := {
"updatecenter.iamworkin.lan",
"updates.iamworkin.lan",
"update.flowercore.io",
"updates.flowercore.io",
}
required_methods := {"GET", "HEAD", "POST", "OPTIONS"}