Compare commits

...

113 Commits

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:49:35 -05:00
Andrew Stoltz
dfaae3cbce deploy(segmentdisplay): roll non-root GX10 image 2026-06-17 10:52:19 -05:00
Andrew Stoltz
0020aa97ce Pin GX10 Network web SEC-5 image 2026-06-17 10:05:12 -05:00
Andrew Stoltz
b353058b47 gx10: deploy hardened MessageBoard web image 2026-06-17 09:41:56 -05:00
Andrew Stoltz
435e60a3f0 Deploy MenuBoard SEC-5 non-root image to GX10 2026-06-17 09:13:01 -05:00
Andrew Stoltz
ed32a65873 Deploy Media SEC-5 non-root image to GX10 2026-06-17 08:47:40 -05:00
Andrew Stoltz
18f4f657f8 Deploy Intranet SEC-5 non-root image to GX10
Pin localhost/fc-intranet-web:v20260617-sec5-intranet-1abdf90 and apply restricted pod/container security contexts plus writable /data, /tmp, and /app/logs mounts.
2026-06-17 08:18:31 -05:00
Andrew Stoltz
cf8cc4ba54 deploy(chat): roll non-root GX10 image 2026-06-17 07:46:28 -05:00
Andrew Stoltz
2cfd340833 deploy(dns): roll non-root GX10 images 2026-06-17 06:36:07 -05:00
Andrew Stoltz
983406b886 deploy(php): roll non-root GX10 web image 2026-06-17 05:59:36 -05:00
Andrew Stoltz
cebd934872 deploy(php): roll non-root GX10 operator image 2026-06-17 05:22:36 -05:00
Andrew Stoltz
8d55ca1566 deploy(mysql): roll non-root GX10 operator image 2026-06-17 04:34:28 -05:00
Andrew Stoltz
b11f26b963 deploy(mysql): roll non-root GX10 web image 2026-06-17 04:08:23 -05:00
Andrew Stoltz
aa0525331d deploy(updater): roll non-root GX10 image 2026-06-17 03:15:35 -05:00
Andrew Stoltz
9ce18e4acc fix(irc): inject GX10 cloak keys from Secret 2026-06-17 02:39:55 -05:00
Andrew Stoltz
11f32f1a6e deploy(dns): add GX10 fc-dns app 2026-06-17 02:12:40 -05:00
Andrew Stoltz
083e7f41cd fix(fc-php): restore missing IngressRoute + TLS cert (php-web 404 on GX10)
php.iamworkin.lan returned 404 on every path: the GX10 GitOps capture grabbed
fc-php's deployment/service but NOT its IngressRoute (chicken-egg — php wasn't
routed at capture time), so Traefik matched no route. Pod is 1/1 Running 37h —
the 404 was pure missing-route, confirmed by diffing against the healthy sibling
mysql-web (which has its IngressRoute).

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 01:57:47 -05:00
Andrew Stoltz
336c4a6ec0 deploy(signage): roll GX10 F2 image 2026-06-17 01:25:04 -05:00
Andrew Stoltz
415fec9e4d gx10-gitops: deploy-loop proof — mark knowledge svc managed-by gx10-argocd 2026-06-16 22:33:40 -05:00
Andrew Stoltz
6c0be8563d gx10-gitops: capture live manifests for 32 product namespaces (ArgoCD adoption source) 2026-06-16 22:24:23 -05:00
Andrew Stoltz
0218b1f8b6 gx10-gitops: pilot — capture live knowledge manifests (adoption source) 2026-06-16 22:18:20 -05:00
210 changed files with 16423 additions and 83 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`. - **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. - **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-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. - **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. - **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. - **ArgoCD must use internal Gitea URL**: `http://gitea-clusterip.gitea.svc.cluster.local:3000/bluejay/bluejay-infra.git`, not the external HTTPS URL (step-ca cert isn't trusted by ArgoCD). The `ApplicationSet` and any hand-created `Application` must both use the internal URL.

View File

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

View File

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

View File

@@ -0,0 +1,111 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "aistation-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "aistation-web",
"namespace": "fc-aistation"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "aistation-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"kubectl.kubernetes.io/restartedAt": "2026-06-16T14:00:18-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5000",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "aistation-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "aistation-web-config"
}
}
],
"image": "localhost/fc-aistation-web:v20260619-aistation-eb9d513",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "aistation-web",
"ports": [
{
"containerPort": 5000,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/healthz",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"nodeSelector": {
"kubernetes.io/arch": "arm64"
},
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "aistation-web-data"
}
}
]
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
# FlowerCore Apple MDM on GX10
This directory deploys the NanoHUB `v0.2.0` substrate for Apple MDM protocol
traffic at `https://mdm.iamworkin.lan`.
## Runtime
- Namespace: `fc-apple-mdm`
- Image: `localhost/fc-apple-mdm-nanohub:v0.2.0-20260617`
- Upstream digest: `ghcr.io/micromdm/nanohub:latest@sha256:e36a50db2dc3d2bf736645e58712f622c04b05b28487390981905ef4d0be5fbd`
- Persistent state: `fc-apple-mdm-data` on `local-path`, mounted at `/var/lib/nanohub`
- File backend DSN: `/var/lib/nanohub/db`
- Required secret: `Secret/fc-apple-mdm-runtime`, key `NANOHUB_API_KEY`
- Optional later bridge secret: `NANOHUB_WEBHOOK_URL`
- Required CA mount: `ConfigMap/fc-apple-mdm-root-ca`, key `root_ca.crt`
- SCEP backend: noc1 systemd service `step-ca-apple-mdm-scep`, forwarded through
selectorless `Service/fc-apple-mdm-scep` and `EndpointSlice/fc-apple-mdm-scep-noc1`
to `10.0.56.10:9080`
NanoHUB API authentication is HTTP Basic with username `nanohub` and password
from `NANOHUB_API_KEY`.
## Public Surface
The Traefik route intentionally exposes only:
- `/version`
- `/mdm`
- `/checkin`
- `/scep`
NanoHUB APIs under `/api/v1/*` stay cluster-internal for MDM-N1. The
DeviceManagement bridge can use the ClusterIP service directly once its NanoHUB
client lane lands.
SCEP is backed by the dedicated Apple-MDM-specific RSA step-ca hierarchy on
noc1, not by the IAmWorkin ACME CA. The live profile URL is:
```text
https://mdm.iamworkin.lan/scep/apple-mdm-scep
```
Do not point `APPLE_MDM_SCEP_URL` at a placeholder URL or at the ECDSA
IAmWorkin ACME CA; Smallstep SCEP requires an RSA intermediate/decrypter path.
## Deployment Notes
1. Create or refresh the runtime Kubernetes Secret from the 1Password item
`FlowerCore Apple MDM Runtime` before sync. GX10 does not yet depend on the
1Password operator for this workload.
2. Import `localhost/fc-apple-mdm-nanohub:v0.2.0-20260617` into GX10 containerd
before ArgoCD syncs. The deployment uses `imagePullPolicy: Never`.
3. Ensure `mdm.iamworkin.lan` resolves to the GX10 Traefik VIP `10.0.57.202`
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,322 @@
# 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
---
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
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.57.202 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-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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,450 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "fc-devicemgmt-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
},
"name": "fc-devicemgmt-web",
"namespace": "fc-devicemgmt"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app": "fc-devicemgmt-web"
}
},
"strategy": {
"rollingUpdate": {
"maxSurge": 0,
"maxUnavailable": 1
},
"type": "RollingUpdate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/healthz",
"flowercore.io/audit-trace-id": "runtime-activity-trace",
"kubectl.kubernetes.io/restartedAt": "2026-05-20T11:29:46-05:00",
"operator.1password.io/last-restarted": "2026-05-20T16:29:03Z",
"prometheus.io/path": "/metrics",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app": "fc-devicemgmt-web",
"app.kubernetes.io/component": "web",
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "fc-devicemgmt-web",
"app.kubernetes.io/part-of": "flowercore",
"flowercore.io/created-by": "bluejay-infra",
"flowercore.io/tenant-id": "system"
}
},
"spec": {
"containers": [
{
"env": [
{
"name": "ASPNETCORE_URLS",
"value": "http://+:8080"
},
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT",
"value": "false"
},
{
"name": "HOME",
"value": "/data"
},
{
"name": "FlowerCore__Service__Name",
"value": "FlowerCore.DeviceManagement.Web"
},
{
"name": "FlowerCore__DeviceManagement__DefaultTenantId",
"value": "system"
},
{
"name": "FlowerCore__Database__Provider",
"value": "Sqlite"
},
{
"name": "FlowerCore__Database__ConnectionStrings__Sqlite",
"value": "Data Source=/data/devicemgmt.db"
},
{
"name": "FlowerCore__Database__Password",
"valueFrom": {
"secretKeyRef": {
"key": "DB-Password",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__Auth__Enabled",
"value": "true"
},
{
"name": "Auth__ApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__Auth__ApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "Auth__AdminApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ADMIN_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__Auth__AdminApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ADMIN_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "Auth__AgentApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_AGENT_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__Auth__AgentApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_AGENT_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityCertificatePem",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__EnrollmentCertificateAuthorityPrivateKeyPem",
"valueFrom": {
"secretKeyRef": {
"key": "DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AgentMtls__ForwardedCertificateHosts__0",
"value": "devices-agent.iamworkin.lan"
},
{
"name": "FlowerCore__DeviceManagement__AgentMtls__ForwardedCertificateHeader",
"value": "X-Forwarded-Tls-Client-Cert"
},
{
"name": "FlowerCore__EventBus__Redis__Configuration",
"value": "redis.fc-redis.svc:6379"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__Enabled",
"value": "true"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__GatewayMode",
"value": "nanohub"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubBaseUrl",
"value": "http://fc-apple-mdm.fc-apple-mdm.svc"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubApiUserName",
"value": "nanohub"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubNanoMdmApiPath",
"value": "/api/v1/nanomdm/"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__EnrollmentProfileDownloadUrl",
"value": "https://devices.iamworkin.lan/api/v1/apple-mdm/enrollment-profile.mobileconfig"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__MdmServerUrl",
"value": "https://mdm.iamworkin.lan/mdm"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__MdmCheckInUrl",
"value": "https://mdm.iamworkin.lan/checkin"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__Organization",
"value": "FlowerCore"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__EnrollmentDisplayName",
"value": "FlowerCore Apple MDM"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepName",
"value": "FlowerCore Apple MDM Device Identity"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__TrustAnchorDisplayName",
"value": "IAmWorkin ACME CA Root CA"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__TrustAnchorCertificatePem",
"value": "-----BEGIN CERTIFICATE-----\nMIIBxDCCAWqgAwIBAgIRAPY357G6ow6zMAL5+4bS2kkwCgYIKoZIzj0EAwIwQDEa\nMBgGA1UEChMRSUFtV29ya2luIEFDTUUgQ0ExIjAgBgNVBAMTGUlBbVdvcmtpbiBB\nQ01FIENBIFJvb3QgQ0EwHhcNMjYwMzA4MTgwNzExWhcNMzYwMzA1MTgwNzExWjBA\nMRowGAYDVQQKExFJQW1Xb3JraW4gQUNNRSBDQTEiMCAGA1UEAxMZSUFtV29ya2lu\nIEFDTUUgQ0EgUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ2n04X1\nJZo5Zdq/i1Idv8+fqwZyAzBh7whbqj0SWsJL8UWRabCMqYCs7+dXO0xRSzqkwFDL\nx+vooOai8RgRNhajRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/\nAgEBMB0GA1UdDgQWBBRnuPPQR6iM/H6vOluiU3Sygayz8jAKBggqhkjOPQQDAgNI\nADBFAiEArQK9dYPGmAZsdYnjziuFVVE5NKZUcceYvGfGC+tLXUsCIAudF2zJrCRq\n3mK50ZZET/fwTkJwiEF4824mjP8p1CKM\n-----END CERTIFICATE-----"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__NanoHubApiKey",
"valueFrom": {
"secretKeyRef": {
"key": "NANOHUB_API_KEY",
"name": "fc-devicemgmt-runtime"
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ApnsTopic",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_APNS_TOPIC",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepUrl",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_SCEP_URL",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ScepChallenge",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_SCEP_CHALLENGE",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ProfileSigningCertificatePem",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_PROFILE_SIGNING_CERTIFICATE_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ProfileSigningPrivateKeyPem",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_PROFILE_SIGNING_PRIVATE_KEY_PEM",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__RequireManagedWifiPayload",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_REQUIRE_MANAGED_WIFI_PAYLOAD",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiSsid",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_MANAGED_WIFI_SSID",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiPassword",
"valueFrom": {
"secretKeyRef": {
"key": "APPLE_MDM_MANAGED_WIFI_PASSWORD",
"name": "fc-devicemgmt-runtime",
"optional": true
}
}
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiEncryptionType",
"value": "WPA2"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiAutoJoin",
"value": "true"
},
{
"name": "FlowerCore__DeviceManagement__AppleMdm__ManagedWifiDisableAssociationMacRandomization",
"value": "true"
}
],
"image": "localhost/fc-devicemgmt-web:v20260619-patchledger-b49b9b9",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"name": "web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "1",
"memory": "768Mi"
},
"requests": {
"cpu": "100m",
"memory": "256Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"startupProbe": {
"failureThreshold": 30,
"initialDelaySeconds": 5,
"periodSeconds": 5,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 1
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "fc-devicemgmt-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "Middleware",
"metadata": {
"name": "devicemgmt-agent-pass-client-cert",
"namespace": "fc-devicemgmt"
},
"spec": {
"passTLSClientCert": {
"pem": true
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "TLSOption",
"metadata": {
"name": "devicemgmt-agent-mtls",
"namespace": "fc-devicemgmt"
},
"spec": {
"clientAuth": {
"clientAuthType": "RequireAndVerifyClientCert",
"secretNames": [
"devicemgmt-agent-client-ca"
]
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,110 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "library-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "library-web",
"namespace": "fc-library"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "library-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/health",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5000",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "library-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "library-web-config"
}
}
],
"image": "localhost/fc-library-web:v20260619-library-0e027cc",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "library-web",
"ports": [
{
"containerPort": 5000,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "library-web-data"
}
}
]
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,143 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "menuboard-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "menuboard-web",
"namespace": "fc-menuboard"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "menuboard-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5000",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "menuboard-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "menuboard-web-config"
}
},
{
"secretRef": {
"name": "menuboard-web-secrets"
}
}
],
"image": "localhost/fc-menuboard-web:v20260617-sec5-menuboard-303a636",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "menuboard-web",
"ports": [
{
"containerPort": 5000,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "temp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "menuboard-web-data"
}
},
{
"emptyDir": {},
"name": "temp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,149 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app": "messageboard-web"
},
"name": "messageboard-web",
"namespace": "fc-messageboard"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app": "messageboard-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/health",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app": "messageboard-web"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "messageboard-web-config"
}
},
{
"secretRef": {
"name": "messageboard-web-secrets",
"optional": true
}
}
],
"image": "localhost/fc-messageboard-web:v20260617-sec5-messageboard-e5f77ef",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"initialDelaySeconds": 10,
"periodSeconds": 30,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 5
},
"name": "messageboard-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"tcpSocket": {
"port": 8080
},
"timeoutSeconds": 5
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "512Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/app/logs",
"name": "logs"
},
{
"mountPath": "/tmp",
"name": "tmp"
}
],
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
}
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "messageboard-web-data"
}
},
{
"name": "logs",
"emptyDir": {}
},
{
"name": "tmp",
"emptyDir": {}
}
]
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,111 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/managed-by": "argocd",
"app.kubernetes.io/name": "retail-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "retail-web",
"namespace": "fc-retail"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 3,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "retail-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"fc.flowercore.io/healthz-anon": "true",
"fc.flowercore.io/probe-path": "/healthz",
"kubectl.kubernetes.io/restartedAt": "2026-06-02T01:34:08-05:00",
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "5000",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "retail-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "retail-web-config"
}
}
],
"image": "localhost/fc-retail-web:v20260619-retail-faae9db",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "retail-web",
"ports": [
{
"containerPort": 5000,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "retail-web-data"
}
}
]
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,147 @@
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"labels": {
"app.kubernetes.io/name": "segmentdisplay-web",
"app.kubernetes.io/part-of": "flowercore"
},
"name": "segmentdisplay-web",
"namespace": "fc-segmentdisplay"
},
"spec": {
"progressDeadlineSeconds": 600,
"replicas": 1,
"revisionHistoryLimit": 10,
"selector": {
"matchLabels": {
"app.kubernetes.io/name": "segmentdisplay-web"
}
},
"strategy": {
"type": "Recreate"
},
"template": {
"metadata": {
"annotations": {
"prometheus.io/path": "/metrics/prometheus",
"prometheus.io/port": "8080",
"prometheus.io/scrape": "true"
},
"labels": {
"app.kubernetes.io/name": "segmentdisplay-web",
"app.kubernetes.io/part-of": "flowercore"
}
},
"spec": {
"containers": [
{
"envFrom": [
{
"configMapRef": {
"name": "segmentdisplay-web-config"
}
},
{
"secretRef": {
"name": "segmentdisplay-web-secrets",
"optional": true
}
}
],
"image": "localhost/fc-segmentdisplay-web:v20260617-sec5-segmentdisplay-7730fb2",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "segmentdisplay-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
},
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch",
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "segmentdisplay-web-data"
}
},
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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