Compare commits

..

99 Commits

Author SHA1 Message Date
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
219 changed files with 16192 additions and 604 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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,69 @@
# 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`. Required before ingress can verify agent client-cert chains. |
| `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 currently uses `RequireAnyClientCert`; the
application remains the authorization boundary by matching the forwarded client
certificate thumbprint to the enrolled device record. Once
`DEVICE_MANAGEMENT_ENROLLMENT_CA_CERTIFICATE_PEM` and
`DEVICE_MANAGEMENT_ENROLLMENT_CA_PRIVATE_KEY_PEM` are present and newly enrolled
agents prove they chain to that CA, create the matching Traefik CA secret and
switch this TLSOption to `RequireAndVerifyClientCert`.
## 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-enrollca-c54623d",
"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,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,13 @@
{
"apiVersion": "traefik.io/v1alpha1",
"kind": "TLSOption",
"metadata": {
"name": "devicemgmt-agent-mtls",
"namespace": "fc-devicemgmt"
},
"spec": {
"clientAuth": {
"clientAuthType": "RequireAnyClientCert"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,542 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: fc-dns
labels:
app.kubernetes.io/part-of: flowercore
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dns-web-data
namespace: fc-dns
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: dns-web-config
namespace: fc-dns
data:
appsettings.Production.json: |
{
"FlowerCore": {
"Auth": {
"Enabled": true,
"Oidc": {
"Enabled": true,
"Audience": "dns",
"RequireHttpsMetadata": true
}
},
"Database": {
"Provider": "Sqlite",
"ConnectionStrings": {
"Sqlite": "Data Source=/data/dns.db"
}
},
"Tenant": {
"DefaultTenantId": "default",
"JwtClaimsEnabled": true,
"DefaultTenantHosts": [
"dns.iamworkin.lan"
]
},
"Audit": {
"HashChain": {
"BridgeSensitivity": {
"Distribution": "Warn"
}
}
},
"Dns": {
"RateLimits": {
"PermitLimit": 60,
"WindowSeconds": 60,
"QueueLimit": 0
}
}
}
}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: dns-web
namespace: fc-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dns-web
rules:
- apiGroups:
- ""
resources:
- namespaces
- pods
- services
- secrets
- configmaps
verbs:
- get
- list
- watch
- apiGroups:
- cert-manager.io
resources:
- certificates
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dns-web
subjects:
- kind: ServiceAccount
name: dns-web
namespace: fc-dns
roleRef:
kind: ClusterRole
name: dns-web
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: dns-web
namespace: fc-dns
labels:
app: dns-web
app.kubernetes.io/name: dns-web
app.kubernetes.io/part-of: flowercore
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: dns-web
template:
metadata:
labels:
app: dns-web
app.kubernetes.io/name: dns-web
app.kubernetes.io/part-of: flowercore
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "5320"
prometheus.io/path: "/metrics/prometheus"
flowercore.io/source-sha: "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:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "library-web",
"ports": [
{
"containerPort": 5000,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 5000,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "library-web-data"
}
}
]
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 30,
"periodSeconds": 30,
"successThreshold": 1,
"timeoutSeconds": 5
},
"name": "presentations-web",
"ports": [
{
"containerPort": 8080,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 6,
"httpGet": {
"path": "/health",
"port": 8080,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 5
},
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/data",
"name": "data",
"subPath": "data"
},
{
"mountPath": "/home/app/wwwroot/uploads",
"name": "data",
"subPath": "uploads"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"initContainers": [
{
"command": [
"/bin/sh",
"-lc",
"set -eu\nmkdir -p /mnt/pvc/data /mnt/pvc/uploads\n\nfor file in presentations.db presentations.db-shm presentations.db-wal; do\n if [ -f \"/mnt/pvc/${file}\" ] && [ ! -f \"/mnt/pvc/data/${file}\" ]; then\n mv \"/mnt/pvc/${file}\" \"/mnt/pvc/data/${file}\"\n fi\ndone\n\nif [ -d /mnt/pvc/dp-keys ] && [ ! -d /mnt/pvc/data/dp-keys ]; then\n mv /mnt/pvc/dp-keys /mnt/pvc/data/dp-keys\nfi\n\nfor directory in imports slides html-bundles; do\n if [ -d \"/mnt/pvc/${directory}\" ] && [ ! -d \"/mnt/pvc/uploads/${directory}\" ]; then\n mv \"/mnt/pvc/${directory}\" \"/mnt/pvc/uploads/${directory}\"\n fi\ndone\n\nmkdir -p \\\n /mnt/pvc/data/dp-keys \\\n /mnt/pvc/uploads/imports \\\n /mnt/pvc/uploads/slides \\\n /mnt/pvc/uploads/html-bundles\n"
],
"image": "localhost/fc-presentations:gx10-v1",
"imagePullPolicy": "Never",
"name": "storage-init",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/mnt/pvc",
"name": "data"
}
]
}
],
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"fsGroupChangePolicy": "OnRootMismatch"
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"name": "data",
"persistentVolumeClaim": {
"claimName": "presentations-web-data"
}
}
]
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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:gx10-v1",
"imagePullPolicy": "Never",
"livenessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5280,
"scheme": "HTTP"
},
"initialDelaySeconds": 20,
"periodSeconds": 20,
"successThreshold": 1,
"timeoutSeconds": 1
},
"name": "signage-replay-web",
"ports": [
{
"containerPort": 5280,
"name": "http",
"protocol": "TCP"
}
],
"readinessProbe": {
"failureThreshold": 3,
"httpGet": {
"path": "/healthz",
"port": 5280,
"scheme": "HTTP"
},
"initialDelaySeconds": 10,
"periodSeconds": 10,
"successThreshold": 1,
"timeoutSeconds": 1
},
"resources": {
"limits": {
"cpu": "500m",
"memory": "256Mi"
},
"requests": {
"cpu": "100m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/tmp",
"name": "tmp"
},
{
"mountPath": "/home/app/logs",
"name": "logs"
}
]
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"schedulerName": "default-scheduler",
"securityContext": {
"fsGroup": 1654,
"runAsGroup": 1654,
"runAsNonRoot": true,
"runAsUser": 1654
},
"terminationGracePeriodSeconds": 30,
"volumes": [
{
"emptyDir": {},
"name": "tmp"
},
{
"emptyDir": {},
"name": "logs"
}
]
}
}
}
}

View File

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

View File

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

View File

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

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