Compare commits
100 Commits
feat/authe
...
34dda0c99c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34dda0c99c | ||
|
|
e1e0159b06 | ||
| cb4ea13e7a | |||
|
|
a3cd67d6bb | ||
|
|
81a3ddac4c | ||
| 300f8ad546 | |||
| fe38c2641f | |||
|
|
3b40dfb185 | ||
| 103878671c | |||
|
|
36039c1335 | ||
| 2a66109f13 | |||
|
|
933fea89d1 | ||
|
|
13f9bb7710 | ||
|
|
9a58fd2af6 | ||
|
|
404d884863 | ||
| f4bd90f805 | |||
|
|
67d67ab73d | ||
|
|
f7d41cdc60 | ||
|
|
2c0afc28e4 | ||
|
|
ba5f5dd0fb | ||
|
|
dc699da7b3 | ||
|
|
1e8bf54c6e | ||
|
|
e2e93d482c | ||
| 4319cc2b51 | |||
|
|
2bf339ce51 | ||
|
|
5bdedfc5ae | ||
|
|
0307ae16ae | ||
|
|
6c18f69cf2 | ||
|
|
47e2256556 | ||
|
|
9d77f8ba0e | ||
|
|
2f4be19c85 | ||
|
|
2a62c40990 | ||
|
|
7be98e5efc | ||
|
|
a65b356c9d | ||
|
|
08c17ef1b4 | ||
|
|
06f2f002b7 | ||
|
|
7ac4a8b4b7 | ||
|
|
90f2a86819 | ||
|
|
cbdefb2b23 | ||
|
|
1c36fe3a0a | ||
|
|
2b420ce8a4 | ||
|
|
5cbc1a06b1 | ||
|
|
9e7ee39b3a | ||
|
|
ae030a5f33 | ||
| bc8c35896f | |||
|
|
2cc91b6df0 | ||
| 0d2090fe81 | |||
|
|
bc3548e715 | ||
| 74333cc26b | |||
|
|
7310fb88c2 | ||
| 148bc87b9a | |||
|
|
2a1e842100 | ||
| bc28430d24 | |||
|
|
cc92272217 | ||
| d6f4468a9c | |||
|
|
2f796a2ebd | ||
| 1f1f6823db | |||
|
|
b92f74b63a | ||
|
|
cb7f7dbc4d | ||
|
|
03126d5584 | ||
|
|
495e884c41 | ||
|
|
65aa1e6104 | ||
|
|
7f2a3b76b4 | ||
| ea73f00461 | |||
|
|
25ace30a03 | ||
|
|
ca574c2280 | ||
|
|
09387f90e1 | ||
|
|
e641ceab48 | ||
|
|
c263426ea5 | ||
|
|
bacac067cf | ||
| 914fed08d8 | |||
|
|
200aeab032 | ||
|
|
8182616d4c | ||
|
|
f0862ac03c | ||
|
|
46c392605e | ||
| 89b147bbdd | |||
| d7238a5e3b | |||
| fc444a02a1 | |||
| 83d4883d55 | |||
| f8fe3b2688 | |||
| f2ab892ebc | |||
| fef68a9560 | |||
|
|
6fe77225ae | ||
| 634b9c4169 | |||
| b8c7e59005 | |||
| 65ac8d6f01 | |||
| 35844e0dbd | |||
| b1e307151e | |||
| 12b07219c7 | |||
| 9fd32c4415 | |||
| ad670fb344 | |||
|
|
6f6ca50987 | ||
|
|
c7be58c1f7 | ||
|
|
a1f5a393cd | ||
|
|
710340d8be | ||
|
|
7d2daaa4f8 | ||
|
|
e50e103ba0 | ||
|
|
e8094eb0bd | ||
| 8d87d9172c | |||
|
|
cfd9743afa |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/.gitattributes text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
11
README.md
11
README.md
@@ -116,8 +116,19 @@ dotnet test tests/bluejay-infra-lint/BluejayInfraLint.Tests.csproj -c Release
|
|||||||
|
|
||||||
That test project sweeps `bluejay-infra/apps/**` plus the canonical sibling `FlowerCore.*\\k8s` manifests that share the same workspace. Matching `conftest.dev` policy files live under `tests/bluejay-infra-lint/conftest.dev/` for environments that also have `conftest` or `opa`.
|
That test project sweeps `bluejay-infra/apps/**` plus the canonical sibling `FlowerCore.*\\k8s` manifests that share the same workspace. Matching `conftest.dev` policy files live under `tests/bluejay-infra-lint/conftest.dev/` for environments that also have `conftest` or `opa`.
|
||||||
|
|
||||||
|
## Non-K8s Pi Artifacts
|
||||||
|
|
||||||
|
Some `apps/*` directories are deployment artifact bundles consumed by Puppet
|
||||||
|
instead of Kubernetes workloads. `apps/fc-signage-pi-player/` carries the
|
||||||
|
Chromium signage Pi player, `apps/fc-divoom-dm-pi-device/` carries the additive
|
||||||
|
edge2 Divoom-as-DeviceManagement-device profile/Hiera contract, and
|
||||||
|
`apps/fc-divoom-tv-pi/` carries the Divoom TV Pi HDMI systemd/Puppet shape.
|
||||||
|
These bundles intentionally avoid Deployment, IngressRoute, Certificate, and
|
||||||
|
OnePasswordItem resources.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
- OpenVox noc1 durability runbook: `docs/runbooks/openvoxserver-quadlet-durability.md`
|
||||||
- Cert-manager recovery playbook: `FlowerCore.Notes/memory/project_cert_manager_recovery_2026_04_22.md`
|
- Cert-manager recovery playbook: `FlowerCore.Notes/memory/project_cert_manager_recovery_2026_04_22.md`
|
||||||
- Why pfSense DNS is required: `FlowerCore.Notes/memory/feedback_pfsense_dns_required_for_acme.md`
|
- Why pfSense DNS is required: `FlowerCore.Notes/memory/feedback_pfsense_dns_required_for_acme.md`
|
||||||
- Public DNS operator host: `https://dns.iamworkin.lan`
|
- Public DNS operator host: `https://dns.iamworkin.lan`
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# Authentik OIDC client registration sweep
|
|
||||||
|
|
||||||
This directory holds the FlowerCore per-service OIDC client secret references
|
|
||||||
for the ADR-093 / ADR-124 Phase 1 step 8 sweep.
|
|
||||||
|
|
||||||
The `clients/*-oidc-client.yaml` manifests are intentionally only
|
|
||||||
`OnePasswordItem` CRDs. The actual 1Password items are created by an operator in
|
|
||||||
the `IAmWorkin` vault with these fields:
|
|
||||||
|
|
||||||
| Field | Purpose |
|
|
||||||
| --- | --- |
|
|
||||||
| `client_id` | Authentik provider client id, default `<slug>` |
|
|
||||||
| `client_secret` | Authentik provider client secret |
|
|
||||||
| `issuer_url` | `https://id.iamworkin.lan/application/o/<slug>/` |
|
|
||||||
|
|
||||||
Run `scripts/authentik-bulk-client-create.py` in dry-run mode first. Live REST
|
|
||||||
mutation requires `--apply`, `AUTHENTIK_TOKEN`, and an operator-provided
|
|
||||||
client-secret JSON file. The script redacts secrets in all normal output.
|
|
||||||
448
apps/authentik/authentik.yaml
Normal file
448
apps/authentik/authentik.yaml
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
# Authentik OIDC backend
|
||||||
|
# ArgoCD-managed. BlueJay Lab.
|
||||||
|
#
|
||||||
|
# Stack:
|
||||||
|
# - PostgreSQL 16 StatefulSet (single replica, Longhorn RWO 5Gi)
|
||||||
|
# - Redis 7 Deployment (no persistence — session/cache only)
|
||||||
|
# - Authentik server + worker Deployments (image ghcr.io/goauthentik/server:2024.12.3)
|
||||||
|
# - Media PVC shared between server + worker (Longhorn RWO 2Gi)
|
||||||
|
# - Certificate via step-ca-acme ClusterIssuer
|
||||||
|
# - Traefik IngressRoute at id.iamworkin.lan
|
||||||
|
#
|
||||||
|
# Secrets come from 1Password item "authentik-credentials" (IAmWorkin vault, id y6i74ch22q5wvm7znquq4nhhcu)
|
||||||
|
# via the OnePasswordItem CRD, materialized into k8s Secret authentik/authentik-credentials.
|
||||||
|
#
|
||||||
|
# Why the discovery URL is /application/o/pimanager/ : Authentik issues per-application OIDC providers.
|
||||||
|
# The pimanager OIDC application/provider is created after the cluster pods are healthy (manual or
|
||||||
|
# via API once the bootstrap token is available — see Notes substrate).
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: authentik
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: bluejay-infra
|
||||||
|
|
||||||
|
---
|
||||||
|
# 1Password operator pulls the authentik-credentials item into a k8s Secret of the same name.
|
||||||
|
# Field labels in 1P become Secret keys: AUTHENTIK_SECRET_KEY, POSTGRES_PASSWORD, REDIS_PASSWORD,
|
||||||
|
# BOOTSTRAP_ADMIN_PASSWORD, BOOTSTRAP_ADMIN_TOKEN, BOOTSTRAP_ADMIN_EMAIL.
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: authentik-credentials
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/authentik-credentials"
|
||||||
|
|
||||||
|
---
|
||||||
|
# Shared media volume for server + worker pods.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: authentik-media
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
storageClassName: longhorn
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
# PostgreSQL 16 StatefulSet — Authentik's primary store.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: authentik-postgres
|
||||||
|
namespace: authentik
|
||||||
|
labels:
|
||||||
|
app: authentik-postgres
|
||||||
|
argocd.argoproj.io/instance: infra-authentik
|
||||||
|
spec:
|
||||||
|
persistentVolumeClaimRetentionPolicy:
|
||||||
|
whenDeleted: Retain
|
||||||
|
whenScaled: Retain
|
||||||
|
podManagementPolicy: OrderedReady
|
||||||
|
serviceName: authentik-postgres
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 10
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: authentik-postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: authentik-postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: postgres:16-alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
name: postgres
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: authentik
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: POSTGRES_PASSWORD
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: authentik
|
||||||
|
- name: POSTGRES_INITDB_ARGS
|
||||||
|
value: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
|
||||||
|
- name: PGDATA
|
||||||
|
value: /var/lib/postgresql/data/pgdata
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["pg_isready", "-U", "authentik"]
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["pg_isready", "-U", "authentik"]
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
resources:
|
||||||
|
requests: { cpu: 100m, memory: 256Mi }
|
||||||
|
limits: { cpu: 1000m, memory: 1Gi }
|
||||||
|
volumeMounts:
|
||||||
|
- name: pgdata
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: pgdata
|
||||||
|
spec:
|
||||||
|
storageClassName: longhorn
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
volumeMode: Filesystem
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: authentik-postgres
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
selector:
|
||||||
|
app: authentik-postgres
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
|
||||||
|
---
|
||||||
|
# Redis 7 — session storage + Celery broker. No persistence needed (cache).
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: authentik-redis
|
||||||
|
namespace: authentik
|
||||||
|
labels:
|
||||||
|
app: authentik-redis
|
||||||
|
argocd.argoproj.io/instance: infra-authentik
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: authentik-redis
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: authentik-redis
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: redis
|
||||||
|
image: redis:7-alpine
|
||||||
|
args:
|
||||||
|
- "--save"
|
||||||
|
- ""
|
||||||
|
- "--appendonly"
|
||||||
|
- "no"
|
||||||
|
- "--requirepass"
|
||||||
|
- "$(REDIS_PASSWORD)"
|
||||||
|
env:
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: REDIS_PASSWORD
|
||||||
|
ports:
|
||||||
|
- containerPort: 6379
|
||||||
|
name: redis
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket: { port: 6379 }
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket: { port: 6379 }
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
resources:
|
||||||
|
requests: { cpu: 50m, memory: 64Mi }
|
||||||
|
limits: { cpu: 500m, memory: 256Mi }
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: authentik-redis
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: authentik-redis
|
||||||
|
ports:
|
||||||
|
- name: redis
|
||||||
|
port: 6379
|
||||||
|
targetPort: 6379
|
||||||
|
|
||||||
|
---
|
||||||
|
# Authentik server Deployment — HTTP frontend on :9000.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: authentik-server
|
||||||
|
namespace: authentik
|
||||||
|
labels:
|
||||||
|
app: authentik-server
|
||||||
|
argocd.argoproj.io/instance: infra-authentik
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate # shares /media RWO PVC with worker
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: authentik-server
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: authentik-server
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
# Authentik image runs as uid 1000 "authentik" but the Longhorn PVC mounts
|
||||||
|
# root:root by default. fsGroup recursively chgrp + chmod g+rwx so the
|
||||||
|
# non-root container can mkdir /media/public during the tenant_files migration.
|
||||||
|
fsGroup: 1000
|
||||||
|
containers:
|
||||||
|
- name: server
|
||||||
|
image: ghcr.io/goauthentik/server:2024.12.3
|
||||||
|
args: ["server"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 9000
|
||||||
|
name: http
|
||||||
|
- containerPort: 9443
|
||||||
|
name: https
|
||||||
|
env:
|
||||||
|
- name: AUTHENTIK_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: AUTHENTIK_SECRET_KEY
|
||||||
|
- name: AUTHENTIK_REDIS__HOST
|
||||||
|
value: authentik-redis
|
||||||
|
- name: AUTHENTIK_REDIS__PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: REDIS_PASSWORD
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__HOST
|
||||||
|
value: authentik-postgres
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__NAME
|
||||||
|
value: authentik
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__USER
|
||||||
|
value: authentik
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: POSTGRES_PASSWORD
|
||||||
|
- name: AUTHENTIK_BOOTSTRAP_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: BOOTSTRAP_ADMIN_PASSWORD
|
||||||
|
- name: AUTHENTIK_BOOTSTRAP_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: BOOTSTRAP_ADMIN_TOKEN
|
||||||
|
- name: AUTHENTIK_BOOTSTRAP_EMAIL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: BOOTSTRAP_ADMIN_EMAIL
|
||||||
|
- name: AUTHENTIK_DISABLE_UPDATE_CHECK
|
||||||
|
value: "true"
|
||||||
|
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
|
||||||
|
value: "false"
|
||||||
|
- name: AUTHENTIK_LOG_LEVEL
|
||||||
|
value: info
|
||||||
|
# First-boot Authentik can take 3+ min on the migration phase
|
||||||
|
# (waiting on DB lock while worker also runs migrations). Initial
|
||||||
|
# delays are generous so kubelet doesn't kill the pod mid-migration;
|
||||||
|
# periodSeconds keeps post-startup probing responsive.
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /-/health/ready/
|
||||||
|
port: 9000
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 12
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /-/health/live/
|
||||||
|
port: 9000
|
||||||
|
initialDelaySeconds: 300
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /-/health/live/
|
||||||
|
port: 9000
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 15
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 40 # 30s + 40*15s = 10.5 min budget
|
||||||
|
resources:
|
||||||
|
requests: { cpu: 150m, memory: 512Mi }
|
||||||
|
limits: { cpu: 1500m, memory: 1Gi }
|
||||||
|
volumeMounts:
|
||||||
|
- name: media
|
||||||
|
mountPath: /media
|
||||||
|
volumes:
|
||||||
|
- name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: authentik-media
|
||||||
|
|
||||||
|
---
|
||||||
|
# Authentik worker Deployment — runs Celery background tasks.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: authentik-worker
|
||||||
|
namespace: authentik
|
||||||
|
labels:
|
||||||
|
app: authentik-worker
|
||||||
|
argocd.argoproj.io/instance: infra-authentik
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate # shares /media RWO PVC with server
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: authentik-worker
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: authentik-worker
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
# Same as server pod — non-root uid 1000 needs PVC group write.
|
||||||
|
fsGroup: 1000
|
||||||
|
containers:
|
||||||
|
- name: worker
|
||||||
|
image: ghcr.io/goauthentik/server:2024.12.3
|
||||||
|
args: ["worker"]
|
||||||
|
env:
|
||||||
|
- name: AUTHENTIK_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: AUTHENTIK_SECRET_KEY
|
||||||
|
- name: AUTHENTIK_REDIS__HOST
|
||||||
|
value: authentik-redis
|
||||||
|
- name: AUTHENTIK_REDIS__PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: REDIS_PASSWORD
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__HOST
|
||||||
|
value: authentik-postgres
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__NAME
|
||||||
|
value: authentik
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__USER
|
||||||
|
value: authentik
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-credentials
|
||||||
|
key: POSTGRES_PASSWORD
|
||||||
|
- name: AUTHENTIK_DISABLE_UPDATE_CHECK
|
||||||
|
value: "true"
|
||||||
|
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
|
||||||
|
value: "false"
|
||||||
|
- name: AUTHENTIK_LOG_LEVEL
|
||||||
|
value: info
|
||||||
|
resources:
|
||||||
|
requests: { cpu: 100m, memory: 256Mi }
|
||||||
|
limits: { cpu: 1000m, memory: 768Mi }
|
||||||
|
volumeMounts:
|
||||||
|
- name: media
|
||||||
|
mountPath: /media
|
||||||
|
volumes:
|
||||||
|
- name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: authentik-media
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: authentik-server
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: authentik-server
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 9000
|
||||||
|
targetPort: 9000
|
||||||
|
- name: https
|
||||||
|
port: 9443
|
||||||
|
targetPort: 9443
|
||||||
|
|
||||||
|
---
|
||||||
|
# step-ca leaf certificate for id.iamworkin.lan.
|
||||||
|
# step-ca container resolver uses pfSense Unbound, so the public A record for id.iamworkin.lan
|
||||||
|
# MUST exist before this Certificate is applied (cert-manager HTTP-01 will silently 2h-backoff
|
||||||
|
# otherwise). Added 2026-05-25 via scripts/pfsense-add-id-host.py.
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: authentik-tls
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
secretName: authentik-tls
|
||||||
|
dnsNames:
|
||||||
|
- id.iamworkin.lan
|
||||||
|
issuerRef:
|
||||||
|
name: step-ca-acme
|
||||||
|
kind: ClusterIssuer
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: authentik
|
||||||
|
namespace: authentik
|
||||||
|
spec:
|
||||||
|
entryPoints: [websecure]
|
||||||
|
routes:
|
||||||
|
- match: Host(`id.iamworkin.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: authentik-server
|
||||||
|
port: 9000
|
||||||
|
tls:
|
||||||
|
secretName: authentik-tls
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: aistation-oidc-client
|
|
||||||
namespace: fc-aistation
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: aistation
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/aistation-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/aistation-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: audit-oidc-client
|
|
||||||
namespace: fc-audit
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: audit
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/audit-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/audit-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: chat-oidc-client
|
|
||||||
namespace: fc-chat
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: chat
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/chat-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/chat-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: distribution-oidc-client
|
|
||||||
namespace: fc-distribution
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: distribution
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/distribution-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/distribution-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: dms-oidc-client
|
|
||||||
namespace: fc-dms
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: dms
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/dms-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/dms-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: dns-oidc-client
|
|
||||||
namespace: fc-dns
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: dns
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/dns-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/dns-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: intranet-oidc-client
|
|
||||||
namespace: intranet
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: intranet
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/intranet-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/intranet-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: irc-oidc-client
|
|
||||||
namespace: irc
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: irc
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/irc-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/irc-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: kiosk-oidc-client
|
|
||||||
namespace: fc-system
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: kiosk
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/kiosk-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/kiosk-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: knowledge-oidc-client
|
|
||||||
namespace: knowledge
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: knowledge
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/knowledge-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/knowledge-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: library-oidc-client
|
|
||||||
namespace: fc-library
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: library
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/library-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/library-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: licensing-oidc-client
|
|
||||||
namespace: fc-licensing
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: licensing
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/licensing-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/licensing-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: llmbridge-oidc-client
|
|
||||||
namespace: fc-llm-bridge
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: llmbridge
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/llmbridge-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/llmbridge-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: media-oidc-client
|
|
||||||
namespace: fc-media
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: media
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/media-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/media-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: menuboard-oidc-client
|
|
||||||
namespace: fc-menuboard
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: menuboard
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/menuboard-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/menuboard-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: messageboard-oidc-client
|
|
||||||
namespace: fc-messageboard
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: messageboard
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/messageboard-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/messageboard-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: mike-bundle-oidc-client
|
|
||||||
namespace: fc-mike-bundle
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: mike-bundle
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/mike-bundle-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/mike-bundle-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: mndot-oidc-client
|
|
||||||
namespace: fc-mndot
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: mndot
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/mndot-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/mndot-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: mysql-oidc-client
|
|
||||||
namespace: fc-mysql
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: mysql
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/mysql-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/mysql-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: php-oidc-client
|
|
||||||
namespace: fc-php
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: php
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/php-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/php-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: pimanager-oidc-client
|
|
||||||
namespace: fc-pimanager
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: pimanager
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/pimanager-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/pimanager-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: presentations-oidc-client
|
|
||||||
namespace: fc-presentations
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: presentations
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/presentations-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/presentations-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: print-oidc-client
|
|
||||||
namespace: fc-print
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: print
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/print-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/print-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: provisioning-oidc-client
|
|
||||||
namespace: fc-provisioning
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: provisioning
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/provisioning-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/provisioning-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: remotedesktop-oidc-client
|
|
||||||
namespace: fc-desktop
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: remotedesktop
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/remotedesktop-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/remotedesktop-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: retail-oidc-client
|
|
||||||
namespace: fc-retail
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: retail
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/retail-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/retail-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: scoreboards-oidc-client
|
|
||||||
namespace: fc-scoreboard
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: scoreboards
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/scoreboards-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/scoreboards-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: segmentdisplay-oidc-client
|
|
||||||
namespace: fc-segmentdisplay
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: segmentdisplay
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/segmentdisplay-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/segmentdisplay-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: signage-oidc-client
|
|
||||||
namespace: fc-signage
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: signage
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/signage-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/signage-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: signalcontrol-oidc-client
|
|
||||||
namespace: fc-signalcontrol
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: signalcontrol
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/signalcontrol-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/signalcontrol-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: telephony-oidc-client
|
|
||||||
namespace: telephony
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: telephony
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/telephony-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/telephony-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: ttsreader-oidc-client
|
|
||||||
namespace: fc-ttsreader
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: ttsreader
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/ttsreader-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/ttsreader-oidc-client"
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
apiVersion: onepassword.com/v1
|
|
||||||
kind: OnePasswordItem
|
|
||||||
metadata:
|
|
||||||
name: worldbuilder-oidc-client
|
|
||||||
namespace: fc-worldbuilder
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/part-of: flowercore
|
|
||||||
app.kubernetes.io/component: authentik-oidc-client
|
|
||||||
flowercore.io/authentik-client-slug: worldbuilder
|
|
||||||
annotations:
|
|
||||||
flowercore.io/onepassword-item: "IAmWorkin/items/worldbuilder-oidc-client"
|
|
||||||
flowercore.io/expected-fields: "client_id,client_secret,issuer_url"
|
|
||||||
spec:
|
|
||||||
itemPath: "vaults/IAmWorkin/items/worldbuilder-oidc-client"
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# ArgoCD's bluejay-infra ApplicationSet sees apps/authentik as one app. Keep
|
|
||||||
# an explicit resource list so the client manifests can live under clients/.
|
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
resources:
|
|
||||||
- clients/library-oidc-client.yaml
|
|
||||||
- clients/retail-oidc-client.yaml
|
|
||||||
- clients/telephony-oidc-client.yaml
|
|
||||||
- clients/knowledge-oidc-client.yaml
|
|
||||||
- clients/llmbridge-oidc-client.yaml
|
|
||||||
- clients/mysql-oidc-client.yaml
|
|
||||||
- clients/php-oidc-client.yaml
|
|
||||||
- clients/signage-oidc-client.yaml
|
|
||||||
- clients/media-oidc-client.yaml
|
|
||||||
- clients/dms-oidc-client.yaml
|
|
||||||
- clients/pimanager-oidc-client.yaml
|
|
||||||
- clients/distribution-oidc-client.yaml
|
|
||||||
- clients/dns-oidc-client.yaml
|
|
||||||
- clients/print-oidc-client.yaml
|
|
||||||
- clients/aistation-oidc-client.yaml
|
|
||||||
- clients/irc-oidc-client.yaml
|
|
||||||
- clients/ttsreader-oidc-client.yaml
|
|
||||||
- clients/chat-oidc-client.yaml
|
|
||||||
- clients/intranet-oidc-client.yaml
|
|
||||||
- clients/remotedesktop-oidc-client.yaml
|
|
||||||
- clients/provisioning-oidc-client.yaml
|
|
||||||
- clients/scoreboards-oidc-client.yaml
|
|
||||||
- clients/mndot-oidc-client.yaml
|
|
||||||
- clients/kiosk-oidc-client.yaml
|
|
||||||
- clients/mike-bundle-oidc-client.yaml
|
|
||||||
- clients/messageboard-oidc-client.yaml
|
|
||||||
- clients/menuboard-oidc-client.yaml
|
|
||||||
- clients/presentations-oidc-client.yaml
|
|
||||||
- clients/segmentdisplay-oidc-client.yaml
|
|
||||||
- clients/signalcontrol-oidc-client.yaml
|
|
||||||
- clients/worldbuilder-oidc-client.yaml
|
|
||||||
- clients/audit-oidc-client.yaml
|
|
||||||
- clients/licensing-oidc-client.yaml
|
|
||||||
195
apps/fc-aistation/fc-aistation.yaml
Normal file
195
apps/fc-aistation/fc-aistation.yaml
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# FlowerCore.AiStation.Web GitOps adoption manifest.
|
||||||
|
#
|
||||||
|
# Authored from the already-live fc-aistation resources on 2026-06-04.
|
||||||
|
# Keep the live image tag, Service ClusterIP, and PVC volumeName unchanged so
|
||||||
|
# ArgoCD adopts in place instead of replacing the workload or data volume.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: aistation-web-data
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
volumeName: pvc-27448d6f-6e66-42a7-a293-73dd8bbd6b3e
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: aistation-web
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
progressDeadlineSeconds: 600
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: aistation-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: "5000"
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||||
|
- envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: aistation-web-config
|
||||||
|
image: localhost/fc-aistation-web:v20260602-aistation-owned-deploy-fix2
|
||||||
|
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
|
||||||
|
restartPolicy: Always
|
||||||
|
schedulerName: default-scheduler
|
||||||
|
securityContext: {}
|
||||||
|
terminationGracePeriodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: aistation-web-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: aistation-web
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.43.211.127
|
||||||
|
clusterIPs:
|
||||||
|
- 10.43.211.127
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: aistation-web-tls
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web-tls
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
dnsNames:
|
||||||
|
- aistation.iamworkin.lan
|
||||||
|
issuerRef:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: step-ca-acme
|
||||||
|
secretName: aistation-web-tls
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: aistation-web
|
||||||
|
namespace: fc-aistation
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: aistation-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-aistation
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`aistation.iamworkin.lan`)
|
||||||
|
services:
|
||||||
|
- name: aistation-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: aistation-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose aistation-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: aistation-web-public
|
||||||
|
# namespace: fc-aistation
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`aistation.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: aistation-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: aistation-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
@@ -1,5 +1,209 @@
|
|||||||
# FlowerCore Chat — TLS + Ingress
|
# FlowerCore Chat
|
||||||
# Deployment and Service managed by deploy script (not ArgoCD)
|
#
|
||||||
|
# ArgoCD-managed workload plus TLS/Ingress. The chat-web-secret remains an
|
||||||
|
# out-of-band Secret until the values are moved into a 1Password-backed item;
|
||||||
|
# the Deployment references it as optional so GitOps can own the workload
|
||||||
|
# without storing secret material in this repo.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: chat-web-config
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
data:
|
||||||
|
ASPNETCORE_ENVIRONMENT: Production
|
||||||
|
ASPNETCORE_URLS: "http://+:8080"
|
||||||
|
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
|
||||||
|
FlowerCore__Auth__Enabled: "false"
|
||||||
|
FlowerCore__Auth__Oidc__Enabled: "true"
|
||||||
|
FlowerCore__Auth__Oidc__Authority: "https://id.iamworkin.lan/application/o/chat/"
|
||||||
|
FlowerCore__Auth__Oidc__Audience: "chat"
|
||||||
|
FlowerCore__Auth__Oidc__ClientId: "chat"
|
||||||
|
FlowerCore__Database__ConnectionStrings__Sqlite: "Data Source=/data/chat.db"
|
||||||
|
# Ollama target. Switched 2026-04-25 from edge1 Pi5 (10.0.57.17) to BLUEJAY-WS
|
||||||
|
# workstation (10.0.56.20, RX 9070 XT 16GB, OLLAMA_HOST=0.0.0.0:11434, Vulkan
|
||||||
|
# backend per feedback_rdna4_vulkan_broken). The Pi5 was timing out every team-
|
||||||
|
# round speaker at the 300s per-turn cap (live-proven 2026-04-25 03:53 UTC,
|
||||||
|
# see feedback_chat_team_round_edge1_too_slow). Workstation has gemma3:4b for
|
||||||
|
# the Cheap tier, plus gemma3:27b/phi4:14b/qwen3:14b for Default/Balanced/Deep.
|
||||||
|
# Piper TTS stays on edge1 below (different service, Pi handles TTS fine).
|
||||||
|
FlowerCore__AI__OllamaBaseUrl: "http://10.0.56.20:11434"
|
||||||
|
FlowerCore__AI__DefaultModelName: "phi4:14b"
|
||||||
|
ChatOptions__BehaviorRuleEngine__OllamaBaseUrl: "http://10.0.56.20:11434"
|
||||||
|
ChatOptions__BehaviorRuleEngine__FallbackOllamaBaseUrl: "http://10.0.57.17:11434"
|
||||||
|
ChatOptions__BehaviorRuleEngine__ModelName: "gemma3:12b"
|
||||||
|
FlowerCore__AI__Memory__UseSharedIndexingAdapter: "true"
|
||||||
|
FlowerCore__AI__Memory__UseOllamaEmbeddings: "true"
|
||||||
|
FlowerCore__AI__Memory__EmbeddingModel: "nomic-embed-text"
|
||||||
|
FlowerCore__AI__Memory__EnableSharedIndexingBackfill: "true"
|
||||||
|
FlowerCore__AI__Memory__SharedIndexingDatabasePath: "/data/chat-memory-index.db"
|
||||||
|
FlowerCore__AI__Skills__Library__LibraryApiUrl: "http://library-web.fc-library.svc.cluster.local"
|
||||||
|
FlowerCore__AI__Skills__Retail__RetailApiUrl: "http://retail-web.fc-retail.svc.cluster.local"
|
||||||
|
FlowerCore__AI__Skills__Intranet__IntranetBaseUrl: "http://intranet-web.intranet.svc.cluster.local"
|
||||||
|
FlowerCore__AI__Skills__Print__PrintMcpBaseUrl: "http://10.0.57.16:5200"
|
||||||
|
FlowerCore__AI__IrcBridge__Enabled: "true"
|
||||||
|
FlowerCore__AI__IrcBridge__DefaultProfileSlug: "it-helpdesk"
|
||||||
|
FlowerCore__AI__IrcBridge__MentionProfileSlug: "it-helpdesk"
|
||||||
|
FlowerCore__AI__IrcBridge__MentionReactiveMode: "mentions-only"
|
||||||
|
FlowerCore__AI__IrcBridge__AllowActionExecution: "false"
|
||||||
|
FlowerCore__AI__Voice__Piper__Host: "10.0.57.17"
|
||||||
|
FlowerCore__AI__Voice__Piper__Port: "10400"
|
||||||
|
FlowerCore__AI__Voice__OutputRoot: "/data/audio"
|
||||||
|
FlowerCore__AI__Voice__RetentionDays: "30"
|
||||||
|
# LLM provider abstraction (ADR-088). Anthropic stays disabled here -- when
|
||||||
|
# an operator wants to enable Claude, they flip Enabled=true and mount
|
||||||
|
# FlowerCore__Anthropic__ApiKey from the onepassword-synced Secret (see
|
||||||
|
# docs/ai-agents/anthropic-integration.md).
|
||||||
|
FlowerCore__Anthropic__Enabled: "false"
|
||||||
|
FlowerCore__Anthropic__BaseUrl: "https://api.anthropic.com"
|
||||||
|
FlowerCore__Anthropic__DefaultModel: "claude-sonnet-4-6"
|
||||||
|
FlowerCore__Anthropic__CheapModel: "claude-haiku-4-5-20251001"
|
||||||
|
FlowerCore__Anthropic__DeepModel: "claude-opus-4-7"
|
||||||
|
FlowerCore__Budget__ResponseCacheEnabled: "true"
|
||||||
|
OTEL_SERVICE_NAME: FlowerCore.Chat
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector.monitoring.svc.cluster.local:4317"
|
||||||
|
OTEL_EXPORTER_OTLP_PROTOCOL: grpc
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: chat-web-data
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: chat-web
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
annotations:
|
||||||
|
fc.flowercore.io/healthz-anon: "true"
|
||||||
|
fc.flowercore.io/probe-path: "/healthz"
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8080"
|
||||||
|
prometheus.io/path: "/metrics/prometheus"
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: rke2-server
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1654
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
containers:
|
||||||
|
- name: chat-web
|
||||||
|
image: localhost/fc-chat-web:v20260603-oidc-authentik
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: chat-web-config
|
||||||
|
- secretRef:
|
||||||
|
name: chat-web-secret
|
||||||
|
optional: true
|
||||||
|
env:
|
||||||
|
- name: FlowerCore__Auth__Oidc__Authority
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chat-oidc-client
|
||||||
|
key: issuer_url
|
||||||
|
optional: true
|
||||||
|
- name: FlowerCore__Auth__Oidc__ClientId
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chat-oidc-client
|
||||||
|
key: client_id
|
||||||
|
optional: true
|
||||||
|
- name: FlowerCore__Auth__Oidc__ClientSecret
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: chat-oidc-client
|
||||||
|
key: client_secret
|
||||||
|
optional: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 6
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: chat-web-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: chat-web
|
||||||
|
namespace: fc-chat
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: chat-web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
---
|
---
|
||||||
apiVersion: cert-manager.io/v1
|
apiVersion: cert-manager.io/v1
|
||||||
kind: Certificate
|
kind: Certificate
|
||||||
@@ -30,3 +234,41 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: chat-web-tls
|
secretName: chat-web-tls
|
||||||
|
---
|
||||||
|
# Public host profile marker. The app treats this header as authoritative for
|
||||||
|
# the public twin, while the internal chat.iamworkin.lan route does not attach
|
||||||
|
# it and keeps the operator-oriented UI.
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: chat-public-profile-header
|
||||||
|
namespace: fc-chat
|
||||||
|
spec:
|
||||||
|
headers:
|
||||||
|
customRequestHeaders:
|
||||||
|
X-FC-Chat-Host-Profile: "public"
|
||||||
|
---
|
||||||
|
# Public Cloudflare-fronted twin for the anonymous chat surface. Operator
|
||||||
|
# paths are intentionally absent from the allowlist below, so /admin,
|
||||||
|
# /operator, /console, /ops, /api/operator, and /operatorhub miss this route
|
||||||
|
# and return Traefik 404 before reaching the pod. Operator action still needed:
|
||||||
|
# create/verify Cloudflare DNS chat.flowercore.io -> public Traefik endpoint
|
||||||
|
# and mirror the cf-origin-flowercore-io TLS secret into namespace fc-chat.
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: chat-web-public
|
||||||
|
namespace: fc-chat
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- 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`))
|
||||||
|
kind: Rule
|
||||||
|
middlewares:
|
||||||
|
- name: chat-public-profile-header
|
||||||
|
services:
|
||||||
|
- name: chat-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: cf-origin-flowercore-io
|
||||||
|
|||||||
@@ -51,3 +51,26 @@ spec:
|
|||||||
port: 8080
|
port: 8080
|
||||||
tls:
|
tls:
|
||||||
secretName: remotedesktop-web-tls
|
secretName: remotedesktop-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose remotedesktop-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: remotedesktop-web-public
|
||||||
|
# namespace: fc-desktop
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`desktop.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: remotedesktop-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: remotedesktop-web
|
||||||
|
# port: 8080
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
26
apps/fc-devicemgmt/1password-item.yaml
Normal file
26
apps/fc-devicemgmt/1password-item.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Runtime secrets for FlowerCore.DeviceManagement.
|
||||||
|
#
|
||||||
|
# OnePasswordItem operator syncs this item into a Kubernetes Secret with the
|
||||||
|
# same name. Expected fields:
|
||||||
|
# DB-Password
|
||||||
|
# mtls-ca.pem
|
||||||
|
# mtls-client.crt
|
||||||
|
# mtls-client.key
|
||||||
|
# mtls-chain.pem
|
||||||
|
#
|
||||||
|
# Do not add literal secret values to this repo. Runtime pods consume the
|
||||||
|
# synced Secret through env vars and read-only mounts.
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-runtime
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt
|
||||||
|
app.kubernetes.io/component: secrets
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/FlowerCore DeviceManagement Runtime"
|
||||||
30
apps/fc-devicemgmt/certificate-web.yaml
Normal file
30
apps/fc-devicemgmt/certificate-web.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Certificate for devices.iamworkin.lan.
|
||||||
|
#
|
||||||
|
# Preflight gate: FlowerCore.DNS / pfSense must contain an explicit A record:
|
||||||
|
# devices.iamworkin.lan -> 10.0.56.200
|
||||||
|
# before this Certificate is synced. step-ca ACME cannot see the CoreDNS
|
||||||
|
# wildcard, so missing pfSense DNS produces cert-manager HTTP-01 backoff
|
||||||
|
# (feedback_pfsense_dns_required_for_acme).
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web-tls
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
flowercore.io/dns-preflight: "devices.iamworkin.lan must resolve to 10.0.56.200 before ACME sync"
|
||||||
|
spec:
|
||||||
|
secretName: fc-devicemgmt-web-tls
|
||||||
|
issuerRef:
|
||||||
|
name: step-ca-acme
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- devices.iamworkin.lan
|
||||||
|
duration: 720h
|
||||||
|
renewBefore: 240h
|
||||||
81
apps/fc-devicemgmt/clusterrole-operator.yaml
Normal file
81
apps/fc-devicemgmt/clusterrole-operator.yaml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- devices.flowercore.io
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- devices.flowercore.io
|
||||||
|
resources:
|
||||||
|
- devices/status
|
||||||
|
- devices/finalizers
|
||||||
|
- devicegroups/status
|
||||||
|
- devicegroups/finalizers
|
||||||
|
- devicepolicies/status
|
||||||
|
- devicepolicies/finalizers
|
||||||
|
- remotecommands/status
|
||||||
|
- remotecommands/finalizers
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- apps
|
||||||
|
resources:
|
||||||
|
- deployments
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
- services
|
||||||
|
- configmaps
|
||||||
|
- secrets
|
||||||
|
- events
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- batch
|
||||||
|
resources:
|
||||||
|
- jobs
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- networkpolicies
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
19
apps/fc-devicemgmt/clusterrolebinding-operator.yaml
Normal file
19
apps/fc-devicemgmt/clusterrolebinding-operator.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
namespace: fc-devicemgmt
|
||||||
109
apps/fc-devicemgmt/deployment-operator.yaml
Normal file
109
apps/fc-devicemgmt/deployment-operator.yaml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# FlowerCore.DeviceManagement Operator.
|
||||||
|
#
|
||||||
|
# KubeOps controller for devices.flowercore.io resources. Operator-created
|
||||||
|
# children must set OwnerReferences + traceability labels/annotations per
|
||||||
|
# k8s-pod-ownership-and-traceability-standard.md. RBAC below grants
|
||||||
|
# apps/deployments/get so the process can resolve its own Deployment UID.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
flowercore.io/traceability-standard: k8s-pod-ownership-and-traceability-standard
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8080"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
flowercore.io/audit-trace-id: "runtime-activity-trace"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: fc-devicemgmt-operator
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1654
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
containers:
|
||||||
|
- name: operator
|
||||||
|
image: localhost/fc-devicemgmt-operator:v20260519-sp34cl3-fix
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
containerPort: 8080
|
||||||
|
env:
|
||||||
|
- name: ASPNETCORE_ENVIRONMENT
|
||||||
|
value: "Production"
|
||||||
|
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||||
|
value: "false"
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: POD_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
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"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 30
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumeMounts:
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: logs
|
||||||
|
mountPath: /app/logs
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: logs
|
||||||
|
emptyDir: {}
|
||||||
154
apps/fc-devicemgmt/deployment-web.yaml
Normal file
154
apps/fc-devicemgmt/deployment-web.yaml
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# FlowerCore.DeviceManagement Web.
|
||||||
|
#
|
||||||
|
# Source repo is expected to ship FlowerCore.DeviceManagement.Web in a later
|
||||||
|
# Sprint 9+ lane. This manifest is static-valid without requiring the image to
|
||||||
|
# exist yet; import localhost/fc-devicemgmt-web:<tag> to all schedulable RKE2
|
||||||
|
# nodes before letting ArgoCD sync a live rollout.
|
||||||
|
#
|
||||||
|
# SCALED TO 0 — 2026-05-19 morning-routine cleanup.
|
||||||
|
# The Web pod cannot start until TWO upstream gaps close:
|
||||||
|
# 1. MySQL DB instance `flowercore_devicemgmt` (user `fc_devicemgmt`) is
|
||||||
|
# provisioned via fc-mysql Manager. The cluster currently has ZERO
|
||||||
|
# MySqlInstanceCrds and no `mysql.fc-mysql.svc:3306` Service, so the
|
||||||
|
# deployment-web container env `FlowerCore__Database__Host=mysql.fc-mysql.svc`
|
||||||
|
# points at nothing. Provision via the fc-mysql Manager UI/REST/MCP.
|
||||||
|
# 2. 1Password vault item `IAmWorkin/FlowerCore DeviceManagement Runtime`
|
||||||
|
# with 5 fields (DB-Password, mtls-ca.pem, mtls-client.crt, mtls-client.key,
|
||||||
|
# mtls-chain.pem) — see apps/fc-devicemgmt/1password-item.yaml. Mint mTLS
|
||||||
|
# from step-ca-agent ClusterIssuer per ADR-126; DB-Password must match the
|
||||||
|
# password configured for the MySQL user.
|
||||||
|
# Re-enable: change replicas back to 2 after both gaps close. The image tag
|
||||||
|
# in this file (v20260512-cx5) MAY also need a refresh — it predates the
|
||||||
|
# Sprint 34 Cl-3 operator fix; Web may have an analogous bug.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
annotations:
|
||||||
|
flowercore.io/traceability-standard: k8s-pod-ownership-and-traceability-standard
|
||||||
|
spec:
|
||||||
|
replicas: 0
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
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"
|
||||||
|
flowercore.io/audit-trace-id: "runtime-activity-trace"
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1654
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
containers:
|
||||||
|
- name: web
|
||||||
|
image: localhost/fc-devicemgmt-web:v20260512-cx5
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||||
|
env:
|
||||||
|
- name: ASPNETCORE_URLS
|
||||||
|
value: "http://+:8080"
|
||||||
|
- name: ASPNETCORE_ENVIRONMENT
|
||||||
|
value: "Production"
|
||||||
|
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||||
|
value: "false"
|
||||||
|
- name: FlowerCore__Service__Name
|
||||||
|
value: "FlowerCore.DeviceManagement.Web"
|
||||||
|
- name: FlowerCore__DeviceManagement__DefaultTenantId
|
||||||
|
value: "system"
|
||||||
|
- name: FlowerCore__Database__Provider
|
||||||
|
value: "MySql"
|
||||||
|
- name: FlowerCore__Database__Host
|
||||||
|
value: "mysql.fc-mysql.svc"
|
||||||
|
- name: FlowerCore__Database__Database
|
||||||
|
value: "flowercore_devicemgmt"
|
||||||
|
- name: FlowerCore__Database__User
|
||||||
|
value: "fc_devicemgmt"
|
||||||
|
- name: FlowerCore__Database__Password
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: fc-devicemgmt-runtime
|
||||||
|
key: DB-Password
|
||||||
|
- name: FlowerCore__DeviceManagement__AgentMtls__CaPath
|
||||||
|
value: "/secrets/devicemgmt-mtls/mtls-ca.pem"
|
||||||
|
- name: FlowerCore__DeviceManagement__AgentMtls__ClientCertificatePath
|
||||||
|
value: "/secrets/devicemgmt-mtls/mtls-client.crt"
|
||||||
|
- name: FlowerCore__DeviceManagement__AgentMtls__ClientKeyPath
|
||||||
|
value: "/secrets/devicemgmt-mtls/mtls-client.key"
|
||||||
|
- name: FlowerCore__EventBus__Redis__Configuration
|
||||||
|
value: "redis.fc-redis.svc:6379"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 768Mi
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
failureThreshold: 30
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
failureThreshold: 3
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumeMounts:
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: logs
|
||||||
|
mountPath: /app/logs
|
||||||
|
- name: devicemgmt-mtls
|
||||||
|
mountPath: /secrets/devicemgmt-mtls
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: logs
|
||||||
|
emptyDir: {}
|
||||||
|
- name: devicemgmt-mtls
|
||||||
|
secret:
|
||||||
|
secretName: fc-devicemgmt-runtime
|
||||||
|
defaultMode: 0400
|
||||||
55
apps/fc-devicemgmt/ingressroute-web.yaml
Normal file
55
apps/fc-devicemgmt/ingressroute-web.yaml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# LAN ingress for FlowerCore.DeviceManagement Web.
|
||||||
|
#
|
||||||
|
# RKE2 Traefik has no built-in ACME resolver configured. Keep TLS certificate
|
||||||
|
# ownership in cert-manager Certificate/fc-devicemgmt-web-tls.
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`devices.iamworkin.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: fc-devicemgmt-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: fc-devicemgmt-web-tls
|
||||||
|
|
||||||
|
# Future public agent/update host gate (OFF by default):
|
||||||
|
#
|
||||||
|
# Do not enable `update.flowercore.io` here until Authentik OIDC Q-OIDC-1
|
||||||
|
# resolves the public-device-management auth model and route ownership with
|
||||||
|
# UpdateCenter. When enabled, use a separate public IngressRoute with an
|
||||||
|
# explicit Method allowlist, public-host auth middleware, and public TLS
|
||||||
|
# certificate strategy. Leaving this as comments keeps ArgoCD from stealing
|
||||||
|
# live UpdateCenter traffic.
|
||||||
|
#
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: fc-devicemgmt-web-public
|
||||||
|
# namespace: fc-devicemgmt
|
||||||
|
# annotations:
|
||||||
|
# flowercore.io/public-host-gate: "disabled-until-Q-OIDC-1"
|
||||||
|
# spec:
|
||||||
|
# entryPoints:
|
||||||
|
# - websecure
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`update.flowercore.io`) && (Method(`GET`) || Method(`HEAD`) || Method(`POST`) || Method(`OPTIONS`))
|
||||||
|
# kind: Rule
|
||||||
|
# services:
|
||||||
|
# - name: fc-devicemgmt-web
|
||||||
|
# port: 80
|
||||||
|
# tls:
|
||||||
|
# secretName: fc-devicemgmt-public-tls
|
||||||
13
apps/fc-devicemgmt/namespace.yaml
Normal file
13
apps/fc-devicemgmt/namespace.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# FlowerCore.DeviceManagement namespace.
|
||||||
|
#
|
||||||
|
# ArgoCD discovers this directory as Application `infra-fc-devicemgmt`.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
224
apps/fc-devicemgmt/network-policy.yaml
Normal file
224
apps/fc-devicemgmt/network-policy.yaml
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# FlowerCore.DeviceManagement NetworkPolicies.
|
||||||
|
#
|
||||||
|
# NetworkPolicies belong in bluejay-infra so ArgoCD owns rebuild state.
|
||||||
|
# Rules include Traefik post-DNAT backend ports per
|
||||||
|
# feedback_netpol_dnat_backend_port and Synology NFS egress for the requested
|
||||||
|
# cold-tier / future artifact path.
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web-isolation
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
# LAN edge: only cluster Traefik should reach the Web pod for
|
||||||
|
# devices.iamworkin.lan.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: traefik-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
# Direct LAN diagnostics are allowed only from FlowerCore LAN/VPN ranges.
|
||||||
|
- from:
|
||||||
|
- 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: 8080
|
||||||
|
protocol: TCP
|
||||||
|
egress:
|
||||||
|
# CoreDNS.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- port: 53
|
||||||
|
protocol: UDP
|
||||||
|
- port: 53
|
||||||
|
protocol: TCP
|
||||||
|
# Database namespace.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: fc-mysql
|
||||||
|
ports:
|
||||||
|
- port: 3306
|
||||||
|
protocol: TCP
|
||||||
|
# Redis backplane for multi-replica SignalR / live-status fan-out.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: fc-redis
|
||||||
|
ports:
|
||||||
|
- port: 6379
|
||||||
|
protocol: TCP
|
||||||
|
# Traefik VIP / in-cluster Traefik for self-callbacks and public URL
|
||||||
|
# generation tests. Include post-DNAT backend ports 8443 + 8080.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.56.200/32
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: traefik-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
protocol: TCP
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
# Agent egress: LAN/VPN devices may run DM Agent in Generic, Kiosk, Pi,
|
||||||
|
# ThinClient, or Server mode. Keep this private-range only.
|
||||||
|
- to:
|
||||||
|
- 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: 80
|
||||||
|
protocol: TCP
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5000
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5001
|
||||||
|
protocol: TCP
|
||||||
|
# Synology NFS cold-tier / artifact mount allowance.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.3/32
|
||||||
|
ports:
|
||||||
|
- port: 2049
|
||||||
|
protocol: TCP
|
||||||
|
- port: 2049
|
||||||
|
protocol: UDP
|
||||||
|
- port: 111
|
||||||
|
protocol: TCP
|
||||||
|
- port: 111
|
||||||
|
protocol: UDP
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator-isolation
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-devicemgmt-operator
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: monitoring
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
egress:
|
||||||
|
# CoreDNS.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- port: 53
|
||||||
|
protocol: UDP
|
||||||
|
- port: 53
|
||||||
|
protocol: TCP
|
||||||
|
# Kubernetes API for KubeOps reconciliation and Deployment UID lookup.
|
||||||
|
- to: []
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 6443
|
||||||
|
protocol: TCP
|
||||||
|
# Agent egress for operator-initiated probes / fallback command dispatch.
|
||||||
|
- to:
|
||||||
|
- 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: 80
|
||||||
|
protocol: TCP
|
||||||
|
- port: 443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- port: 8443
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5000
|
||||||
|
protocol: TCP
|
||||||
|
- port: 5001
|
||||||
|
protocol: TCP
|
||||||
|
# Synology NFS allowance for future cold-tier/audit archival jobs.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 10.0.58.3/32
|
||||||
|
ports:
|
||||||
|
- port: 2049
|
||||||
|
protocol: TCP
|
||||||
|
- port: 2049
|
||||||
|
protocol: UDP
|
||||||
|
- port: 111
|
||||||
|
protocol: TCP
|
||||||
|
- port: 111
|
||||||
|
protocol: UDP
|
||||||
22
apps/fc-devicemgmt/service-web.yaml
Normal file
22
apps/fc-devicemgmt/service-web.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-web
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-web
|
||||||
|
app.kubernetes.io/component: web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: fc-devicemgmt-web
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
12
apps/fc-devicemgmt/serviceaccount-operator.yaml
Normal file
12
apps/fc-devicemgmt/serviceaccount-operator.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: fc-devicemgmt-operator
|
||||||
|
namespace: fc-devicemgmt
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-devicemgmt-operator
|
||||||
|
app.kubernetes.io/component: operator
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
flowercore.io/tenant-id: system
|
||||||
|
flowercore.io/created-by: bluejay-infra
|
||||||
@@ -74,6 +74,14 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
itemPath: "vaults/IAmWorkin/items/FlowerCore Edition Signing Key - edition:aistation-field"
|
itemPath: "vaults/IAmWorkin/items/FlowerCore Edition Signing Key - edition:aistation-field"
|
||||||
---
|
---
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: distribution-oidc-client
|
||||||
|
namespace: fc-distribution
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/distribution-oidc-client"
|
||||||
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -101,6 +109,7 @@ spec:
|
|||||||
prometheus.io/scrape: "true"
|
prometheus.io/scrape: "true"
|
||||||
prometheus.io/port: "8080"
|
prometheus.io/port: "8080"
|
||||||
prometheus.io/path: "/metrics"
|
prometheus.io/path: "/metrics"
|
||||||
|
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
||||||
spec:
|
spec:
|
||||||
# Synology NFS export `/volume1/kubernetes` ACL only allows rke2-server
|
# Synology NFS export `/volume1/kubernetes` ACL only allows rke2-server
|
||||||
# (10.0.56.11) right now. Until the ACL is widened in DSM (admin only),
|
# (10.0.56.11) right now. Until the ACL is widened in DSM (admin only),
|
||||||
@@ -118,7 +127,7 @@ spec:
|
|||||||
# dotnet.exe publish -c Release -o deploy/app \
|
# dotnet.exe publish -c Release -o deploy/app \
|
||||||
# src/FlowerCore.Distribution.Web/FlowerCore.Distribution.Web.csproj
|
# src/FlowerCore.Distribution.Web/FlowerCore.Distribution.Web.csproj
|
||||||
# podman build -t localhost/fc-distribution:v<tag> -f deploy/Dockerfile.deploy deploy
|
# podman build -t localhost/fc-distribution:v<tag> -f deploy/Dockerfile.deploy deploy
|
||||||
image: localhost/fc-distribution:v202605061948
|
image: localhost/fc-distribution:v20260604-oidc-root-anon
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
@@ -130,6 +139,25 @@ spec:
|
|||||||
value: "Production"
|
value: "Production"
|
||||||
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
|
||||||
value: "false"
|
value: "false"
|
||||||
|
# Authentik/OIDC enforcement. Public read/entitlement + the
|
||||||
|
# dist.flowercore.io Method() allowlist stay open; OIDC gates the
|
||||||
|
# operator/admin surface while /healthz remains anonymous.
|
||||||
|
- 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:
|
||||||
|
name: distribution-oidc-client
|
||||||
|
key: client_secret
|
||||||
|
optional: true
|
||||||
# SQLite connection (catalog + data-protection keys via FlowerCoreDbContext).
|
# SQLite connection (catalog + data-protection keys via FlowerCoreDbContext).
|
||||||
# Read by Data/DatabaseProviderExtensions.cs in precedence order; Sqlite key wins.
|
# Read by Data/DatabaseProviderExtensions.cs in precedence order; Sqlite key wins.
|
||||||
- name: FlowerCore__Database__Provider
|
- name: FlowerCore__Database__Provider
|
||||||
|
|||||||
45
apps/fc-divoom-dm-pi-device/README.md
Normal file
45
apps/fc-divoom-dm-pi-device/README.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# FlowerCore Divoom DM Pi Device
|
||||||
|
|
||||||
|
Source-controlled Puppet/Hiera deployment contract for registering the edge2
|
||||||
|
Divoom MiniToo panel as a FlowerCore DeviceManagement-managed Pi device.
|
||||||
|
|
||||||
|
This is not a Kubernetes application. The live panel remains the existing
|
||||||
|
edge2 `flowercore-divoom.service` managed by `FlowerCore.Puppet`
|
||||||
|
`profile::pi::service::divoom`, with the .NET payload deployed out of band
|
||||||
|
and `/opt/flowercore/divoom/data` plus the Bluetooth shell wrappers preserved.
|
||||||
|
Because edge2 is already Hiera-driven through `profile::pi::service::apps`,
|
||||||
|
the deploy home is additive `profile::pi::service` data/profile source, not
|
||||||
|
`profile::edge::service::apps` and not an ArgoCD/K8s app.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Stage DeviceManagement registration metadata for the edge2 Divoom MiniToo.
|
||||||
|
- Stage a separate, disabled-by-default DM Agent executor unit for privileged
|
||||||
|
Bluetooth operations once the DM-RPC lane lands.
|
||||||
|
- Keep `flowercore-divoom.service` and `flowercore-divoom-bt.service`
|
||||||
|
untouched: no service replacement, no restart subscription, no K8s surface.
|
||||||
|
- Preserve the current wrapper contract:
|
||||||
|
`/opt/flowercore/divoom/bt-link.sh`,
|
||||||
|
`/opt/flowercore/divoom/bt-reset.sh`, and
|
||||||
|
`/opt/flowercore/divoom/audio-link.sh`.
|
||||||
|
- Keep FM radio disabled and require visible render proof; device-info echo is
|
||||||
|
not render proof.
|
||||||
|
|
||||||
|
## Artifact Map
|
||||||
|
|
||||||
|
| Path | Use |
|
||||||
|
| --- | --- |
|
||||||
|
| `hiera/edge2-divoom-dm-device.overlay.yaml` | Additive Hiera overlay for edge2. Merge into the existing node YAML without removing `fc-pimanager` or `fc-divoom`. |
|
||||||
|
| `puppet/profile/pi/service/divoom_dm_device.pp` | Puppet profile shape to vendor into `FlowerCore.Puppet` after the DM-RPC executor binary exists. |
|
||||||
|
| `puppet/templates/divoom-device-registration.json.epp` | DM device registration metadata rendered on edge2. |
|
||||||
|
| `puppet/templates/flowercore-divoom-dm-agent.service.epp` | Separate DM Agent systemd unit. Defaults are stopped and disabled until a later cutover. |
|
||||||
|
|
||||||
|
## Rollout Notes
|
||||||
|
|
||||||
|
1. Land these artifacts in bluejay-infra as the deploy contract.
|
||||||
|
2. Vendor the Puppet profile and EPP templates into `FlowerCore.Puppet`.
|
||||||
|
3. Merge the Hiera overlay into `data/nodes/edge2.iamworkin.lan.yaml`.
|
||||||
|
4. Run Puppet in noop first, preferably with a node-local validation directory
|
||||||
|
under `~/.fcv` rather than `/tmp`.
|
||||||
|
5. Only enable the DM Agent service after the DeviceManagement BT executor has
|
||||||
|
landed and passed operator-eyeball render proof.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
# Merge into FlowerCore.Puppet data/nodes/edge2.iamworkin.lan.yaml.
|
||||||
|
# Additive overlay only: keep the existing fc-pimanager version/tarball entry,
|
||||||
|
# keep fc-divoom enabled, and do not move Divoom into Kubernetes.
|
||||||
|
|
||||||
|
profile::pi::service::apps:
|
||||||
|
fc-pimanager:
|
||||||
|
binary: 'FlowerCore.PiManager.Web'
|
||||||
|
install_dir: '/opt/fc-pimanager'
|
||||||
|
port: 5000
|
||||||
|
environment: 'edge2'
|
||||||
|
version: '2026.05.28.1646'
|
||||||
|
tarball_source: 'puppet:///modules/profile/pi/builds/fc-pimanager.tar.gz'
|
||||||
|
fc-divoom:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
profile::pi::service::divoom_dm_device::ensure: 'present'
|
||||||
|
profile::pi::service::divoom_dm_device::service_enabled: false
|
||||||
|
profile::pi::service::divoom_dm_device::service_ensure: 'stopped'
|
||||||
|
profile::pi::service::divoom_dm_device::device_id: 'edge2-divoom-minitoo'
|
||||||
|
profile::pi::service::divoom_dm_device::display_name: 'edge2 Divoom MiniToo'
|
||||||
|
profile::pi::service::divoom_dm_device::host_fqdn: 'edge2.iamworkin.lan'
|
||||||
|
profile::pi::service::divoom_dm_device::dm_web_url: 'https://devicemgmt.iamworkin.lan'
|
||||||
|
profile::pi::service::divoom_dm_device::divoom_install_dir: '/opt/flowercore/divoom'
|
||||||
|
profile::pi::service::divoom_dm_device::agent_install_dir: '/opt/flowercore/devicemanagement-agent'
|
||||||
|
profile::pi::service::divoom_dm_device::bt_candidate_channels:
|
||||||
|
- '1'
|
||||||
|
- '10'
|
||||||
|
profile::pi::service::divoom_dm_device::default_bt_channel: '1'
|
||||||
|
profile::pi::service::divoom_dm_device::a2dp_default_state: 'off'
|
||||||
|
profile::pi::service::divoom_dm_device::fm_radio_enabled: false
|
||||||
|
profile::pi::service::divoom_dm_device::visible_render_proof_required: true
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
# Drop into FlowerCore.Puppet site-modules/profile/manifests/pi/service/divoom_dm_device.pp.
|
||||||
|
# This profile is additive to profile::pi::service::divoom. It must not manage,
|
||||||
|
# restart, replace, or subscribe the existing flowercore-divoom.service.
|
||||||
|
class profile::pi::service::divoom_dm_device (
|
||||||
|
Enum['present', 'absent'] $ensure = 'present',
|
||||||
|
Boolean $service_enabled = false,
|
||||||
|
Enum['running', 'stopped'] $service_ensure = 'stopped',
|
||||||
|
String $service_name = 'flowercore-divoom-dm-agent',
|
||||||
|
String $device_id = 'edge2-divoom-minitoo',
|
||||||
|
String $display_name = 'edge2 Divoom MiniToo',
|
||||||
|
String $host_fqdn = 'edge2.iamworkin.lan',
|
||||||
|
String $dm_web_url = 'https://devicemgmt.iamworkin.lan',
|
||||||
|
String $divoom_install_dir = '/opt/flowercore/divoom',
|
||||||
|
String $agent_install_dir = '/opt/flowercore/devicemanagement-agent',
|
||||||
|
String $agent_binary = 'FlowerCore.DeviceManagement.Agent',
|
||||||
|
Array[String] $bt_candidate_channels = ['1', '10'],
|
||||||
|
String $default_bt_channel = '1',
|
||||||
|
Enum['on', 'off'] $a2dp_default_state = 'off',
|
||||||
|
Boolean $fm_radio_enabled = false,
|
||||||
|
Boolean $visible_render_proof_required = true,
|
||||||
|
) {
|
||||||
|
include profile::workstation::safe_account_exclusion
|
||||||
|
|
||||||
|
$safe_account = $profile::workstation::safe_account_exclusion::safe_account
|
||||||
|
$config_dir = '/etc/flowercore/device-management/devices'
|
||||||
|
$state_dir = '/var/lib/flowercore/divoom-dm-agent'
|
||||||
|
$log_dir = '/var/log/flowercore/divoom-dm-agent'
|
||||||
|
$registration_path = "${config_dir}/${device_id}.json"
|
||||||
|
$agent_binary_path = "${agent_install_dir}/${agent_binary}"
|
||||||
|
$bt_channels_json = inline_template('[<%= @bt_candidate_channels.map { |c| "\"#{c}\"" }.join(", ") %>]')
|
||||||
|
|
||||||
|
if $safe_account {
|
||||||
|
notify { 'fc-divoom-dm-device safe-account exclusion':
|
||||||
|
message => 'SAFE-ACCOUNT-EXCLUSION: Divoom DM Pi device profile refused to apply on operator workstation',
|
||||||
|
}
|
||||||
|
|
||||||
|
if $facts['os']['family'] != 'windows' {
|
||||||
|
ensure_resource('file', '/var/log/flowercore-audit', {
|
||||||
|
'ensure' => 'directory',
|
||||||
|
'owner' => 'root',
|
||||||
|
'group' => 'root',
|
||||||
|
'mode' => '0755',
|
||||||
|
})
|
||||||
|
|
||||||
|
file { '/var/log/flowercore-audit/safe-account-noop-fc-divoom-dm-device.log':
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => "noop: divoom dm pi device profile refused to apply on safe-account host\n",
|
||||||
|
require => File['/var/log/flowercore-audit'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elsif $ensure == 'absent' {
|
||||||
|
service { $service_name:
|
||||||
|
ensure => stopped,
|
||||||
|
enable => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [
|
||||||
|
"/etc/systemd/system/${service_name}.service",
|
||||||
|
$registration_path,
|
||||||
|
]:
|
||||||
|
ensure => absent,
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-dm-agent-systemd-reload':
|
||||||
|
command => '/usr/bin/systemctl daemon-reload',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
case $facts['os']['family'] {
|
||||||
|
'Debian': {}
|
||||||
|
default: { fail("profile::pi::service::divoom_dm_device only supports Debian-family OS, got ${facts['os']['family']}") }
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [$config_dir, $state_dir, $log_dir]:
|
||||||
|
ensure => directory,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0755',
|
||||||
|
}
|
||||||
|
|
||||||
|
file { $registration_path:
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => epp('profile/pi/fc_divoom_dm/divoom-device-registration.json.epp', {
|
||||||
|
'device_id' => $device_id,
|
||||||
|
'display_name' => $display_name,
|
||||||
|
'host_fqdn' => $host_fqdn,
|
||||||
|
'divoom_install_dir' => $divoom_install_dir,
|
||||||
|
'bt_channels_json' => $bt_channels_json,
|
||||||
|
'default_bt_channel' => $default_bt_channel,
|
||||||
|
'a2dp_default_state' => $a2dp_default_state,
|
||||||
|
'fm_radio_enabled' => $fm_radio_enabled,
|
||||||
|
'visible_render_proof_required' => $visible_render_proof_required,
|
||||||
|
}),
|
||||||
|
require => File[$config_dir],
|
||||||
|
}
|
||||||
|
|
||||||
|
file { "/etc/systemd/system/${service_name}.service":
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => epp('profile/pi/fc_divoom_dm/flowercore-divoom-dm-agent.service.epp', {
|
||||||
|
'service_name' => $service_name,
|
||||||
|
'device_id' => $device_id,
|
||||||
|
'dm_web_url' => $dm_web_url,
|
||||||
|
'registration_path' => $registration_path,
|
||||||
|
'divoom_install_dir' => $divoom_install_dir,
|
||||||
|
'agent_install_dir' => $agent_install_dir,
|
||||||
|
'agent_binary_path' => $agent_binary_path,
|
||||||
|
'state_dir' => $state_dir,
|
||||||
|
'log_dir' => $log_dir,
|
||||||
|
}),
|
||||||
|
notify => Exec['fc-divoom-dm-agent-systemd-reload'],
|
||||||
|
require => File[$registration_path],
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-dm-agent-systemd-reload':
|
||||||
|
command => '/usr/bin/systemctl daemon-reload',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
|
||||||
|
service { $service_name:
|
||||||
|
ensure => $service_ensure,
|
||||||
|
enable => $service_enabled,
|
||||||
|
require => [
|
||||||
|
File["/etc/systemd/system/${service_name}.service"],
|
||||||
|
File[$registration_path],
|
||||||
|
Exec['fc-divoom-dm-agent-systemd-reload'],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"deviceId": "<%= $device_id %>",
|
||||||
|
"displayName": "<%= $display_name %>",
|
||||||
|
"hostFqdn": "<%= $host_fqdn %>",
|
||||||
|
"kind": "DivoomMiniToo",
|
||||||
|
"managedBy": "FlowerCore.DeviceManagement",
|
||||||
|
"executionMode": "Pi",
|
||||||
|
"transport": {
|
||||||
|
"kind": "BluetoothSerial",
|
||||||
|
"candidateChannels": <%= $bt_channels_json %>,
|
||||||
|
"defaultChannel": "<%= $default_bt_channel %>",
|
||||||
|
"deviceInfoIsRenderProof": false,
|
||||||
|
"visibleRenderProofRequired": <%= $visible_render_proof_required %>
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"divoomInstallDir": "<%= $divoom_install_dir %>",
|
||||||
|
"btLink": "<%= $divoom_install_dir %>/bt-link.sh",
|
||||||
|
"btReset": "<%= $divoom_install_dir %>/bt-reset.sh",
|
||||||
|
"audioLink": "<%= $divoom_install_dir %>/audio-link.sh"
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"supportsBluetoothSerial": true,
|
||||||
|
"supportsBtChannelRedetect": true,
|
||||||
|
"supportsBtHardReset": true,
|
||||||
|
"supportsBtAudioProfileSwitch": true,
|
||||||
|
"a2dpDefaultState": "<%= $a2dp_default_state %>",
|
||||||
|
"fmRadioEnabled": <%= $fm_radio_enabled %>
|
||||||
|
},
|
||||||
|
"safety": {
|
||||||
|
"preserveExistingService": "flowercore-divoom.service",
|
||||||
|
"preserveDataDirectory": "<%= $divoom_install_dir %>/data",
|
||||||
|
"doNotEnableFmRadio": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Divoom DM Agent Bluetooth executor
|
||||||
|
Documentation=https://github.com/astoltz/FlowerCore.Notes/blob/master/docs/standards/divoom-tv-hdmi-multitarget-render-substrate.md
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target bluetooth.service
|
||||||
|
Requires=bluetooth.service
|
||||||
|
ConditionPathExists=<%= $agent_binary_path %>
|
||||||
|
ConditionPathExists=<%= $registration_path %>
|
||||||
|
ConditionPathExists=<%= $divoom_install_dir %>/bt-link.sh
|
||||||
|
ConditionPathExists=<%= $divoom_install_dir %>/bt-reset.sh
|
||||||
|
ConditionPathExists=<%= $divoom_install_dir %>/audio-link.sh
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=stoltz
|
||||||
|
Group=stoltz
|
||||||
|
WorkingDirectory=<%= $agent_install_dir %>
|
||||||
|
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
Environment=FLOWERCORE_DM_DEVICE_REGISTRATION=<%= $registration_path %>
|
||||||
|
Environment=Divoom__Bluetooth__DeviceInfoIsRenderProof=false
|
||||||
|
Environment=Divoom__Bluetooth__VisibleRenderProofRequired=true
|
||||||
|
Environment=Divoom__Bluetooth__A2dpDefaultState=off
|
||||||
|
ExecStart=<%= $agent_binary_path %> --mode=Pi --device-id=<%= $device_id %> --dm-web-url=<%= $dm_web_url %> --registration=<%= $registration_path %>
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10s
|
||||||
|
StartLimitBurst=3
|
||||||
|
StartLimitIntervalSec=300s
|
||||||
|
SupplementaryGroups=bluetooth audio dialout
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=<%= $state_dir %> <%= $log_dir %>
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
44
apps/fc-divoom-tv-pi/README.md
Normal file
44
apps/fc-divoom-tv-pi/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# FlowerCore Divoom TV Pi HDMI
|
||||||
|
|
||||||
|
Source-controlled deploy shape for the native `FlowerCore.Divoom.Tv`
|
||||||
|
Avalonia HDMI renderer on a Raspberry Pi connected to a TV.
|
||||||
|
|
||||||
|
This is a Puppet/systemd appliance bundle, not a Kubernetes application. It
|
||||||
|
mirrors the existing `fc-signage-pi-player` pattern: bluejay-infra carries the
|
||||||
|
systemd units, scripts, Hiera shape, and Puppet profile source that
|
||||||
|
`FlowerCore.Puppet` vendors and installs.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Launch the future `FlowerCore.Divoom.Tv` linux-arm64 self-contained payload
|
||||||
|
from `/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv`.
|
||||||
|
- Prefer `cage` as the Wayland fullscreen compositor, with direct app launch as
|
||||||
|
a fallback for development images.
|
||||||
|
- Restart the app after HDMI hotplug with a 2 second DRM settle delay.
|
||||||
|
- Keep all runtime state local: `/var/lib/fc-divoom-tv` and
|
||||||
|
`/var/log/fc-divoom-tv`.
|
||||||
|
- Avoid CDN/runtime fetches; the app renders the in-house Divoom scene catalog
|
||||||
|
locally.
|
||||||
|
|
||||||
|
## Artifact Map
|
||||||
|
|
||||||
|
| Path | Use |
|
||||||
|
| --- | --- |
|
||||||
|
| `systemd/flowercore-divoom-tv.service` | Fullscreen Avalonia HDMI app service. |
|
||||||
|
| `systemd/flowercore-divoom-tv-hdmi.service` | HDMI hotplug responder service. |
|
||||||
|
| `systemd/99-flowercore-divoom-tv-hdmi.rules` | DRM udev hotplug rule. |
|
||||||
|
| `scripts/flowercore-divoom-tv-prelaunch.sh` | Preflight checks and local directory creation. |
|
||||||
|
| `scripts/flowercore-divoom-tv-launch.sh` | Cage-first fullscreen launcher. |
|
||||||
|
| `scripts/flowercore-divoom-tv-hdmi-respond.sh` | Hotplug settle and restart script. |
|
||||||
|
| `puppet/profile/pi/service/divoom_tv.pp` | Puppet profile shape to vendor into `FlowerCore.Puppet`. |
|
||||||
|
| `hiera/example-divoom-tv-pi.iamworkin.lan.yaml` | Example node Hiera for a Divoom TV Pi. |
|
||||||
|
|
||||||
|
## Rollout Notes
|
||||||
|
|
||||||
|
1. Build `FlowerCore.Divoom.Tv` with `dotnet.exe publish -c Release -r linux-arm64 --self-contained`.
|
||||||
|
2. Stage the payload to `/opt/flowercore/divoom-tv/` through the standard noc1
|
||||||
|
jump path and avoid `/tmp` for unprivileged Pi scratch.
|
||||||
|
3. Vendor the profile and static files into `FlowerCore.Puppet`.
|
||||||
|
4. Run Puppet noop, then apply on the target Pi.
|
||||||
|
5. Prove deployment with `systemctl is-active flowercore-divoom-tv.service`,
|
||||||
|
journal lines showing frames presented, and a visible HDMI display check.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
# Example node data for a dedicated Pi -> HDMI -> TV Divoom renderer.
|
||||||
|
# Copy into FlowerCore.Puppet data/nodes/<hostname>.iamworkin.lan.yaml only
|
||||||
|
# after the Pi has a static DHCP/DNS entry and the linux-arm64 payload exists.
|
||||||
|
|
||||||
|
facts:
|
||||||
|
role: pi_prototype
|
||||||
|
|
||||||
|
profile::motd::role: 'Divoom TV HDMI Renderer'
|
||||||
|
|
||||||
|
profile::pi::service::divoom_tv::ensure: 'present'
|
||||||
|
profile::pi::service::divoom_tv::service_enabled: true
|
||||||
|
profile::pi::service::divoom_tv::service_ensure: 'running'
|
||||||
|
profile::pi::service::divoom_tv::install_dir: '/opt/flowercore/divoom-tv'
|
||||||
|
profile::pi::service::divoom_tv::state_dir: '/var/lib/fc-divoom-tv'
|
||||||
|
profile::pi::service::divoom_tv::log_dir: '/var/log/fc-divoom-tv'
|
||||||
|
profile::pi::service::divoom_tv::presentation_mode: 'PillarboxSquare'
|
||||||
|
profile::pi::service::divoom_tv::startup_scene: 'bluejay-clock'
|
||||||
|
profile::pi::service::divoom_tv::reduced_motion: false
|
||||||
149
apps/fc-divoom-tv-pi/puppet/profile/pi/service/divoom_tv.pp
Normal file
149
apps/fc-divoom-tv-pi/puppet/profile/pi/service/divoom_tv.pp
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Drop into FlowerCore.Puppet site-modules/profile/manifests/pi/service/divoom_tv.pp.
|
||||||
|
# Static files come from profile/pi/fc_divoom_tv/ after this bluejay-infra
|
||||||
|
# bundle is vendored into the Puppet control repo.
|
||||||
|
class profile::pi::service::divoom_tv (
|
||||||
|
Enum['present', 'absent'] $ensure = 'present',
|
||||||
|
Boolean $service_enabled = false,
|
||||||
|
Enum['running', 'stopped'] $service_ensure = 'stopped',
|
||||||
|
String $service_name = 'flowercore-divoom-tv',
|
||||||
|
String $user = 'fc-divoom-tv',
|
||||||
|
String $group = 'fc-divoom-tv',
|
||||||
|
String $install_dir = '/opt/flowercore/divoom-tv',
|
||||||
|
String $state_dir = '/var/lib/fc-divoom-tv',
|
||||||
|
String $log_dir = '/var/log/fc-divoom-tv',
|
||||||
|
String $presentation_mode = 'PillarboxSquare',
|
||||||
|
String $startup_scene = 'bluejay-clock',
|
||||||
|
Boolean $reduced_motion = false,
|
||||||
|
) {
|
||||||
|
include profile::workstation::safe_account_exclusion
|
||||||
|
|
||||||
|
$safe_account = $profile::workstation::safe_account_exclusion::safe_account
|
||||||
|
|
||||||
|
if $safe_account {
|
||||||
|
notify { 'fc-divoom-tv safe-account exclusion':
|
||||||
|
message => 'SAFE-ACCOUNT-EXCLUSION: Divoom TV Pi profile refused to apply on operator workstation',
|
||||||
|
}
|
||||||
|
} elsif $ensure == 'absent' {
|
||||||
|
service { $service_name:
|
||||||
|
ensure => stopped,
|
||||||
|
enable => false,
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [
|
||||||
|
"/etc/systemd/system/${service_name}.service",
|
||||||
|
"/etc/systemd/system/${service_name}-hdmi.service",
|
||||||
|
'/etc/udev/rules.d/99-flowercore-divoom-tv-hdmi.rules',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-prelaunch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-launch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-hdmi-respond.sh',
|
||||||
|
'/etc/flowercore/divoom-tv.env',
|
||||||
|
]:
|
||||||
|
ensure => absent,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
case $facts['os']['family'] {
|
||||||
|
'Debian': {}
|
||||||
|
default: { fail("profile::pi::service::divoom_tv only supports Debian-family OS, got ${facts['os']['family']}") }
|
||||||
|
}
|
||||||
|
|
||||||
|
package { ['cage', 'libgbm1', 'libdrm2', 'libxkbcommon0', 'fonts-dejavu-core']:
|
||||||
|
ensure => installed,
|
||||||
|
}
|
||||||
|
|
||||||
|
group { $group:
|
||||||
|
ensure => present,
|
||||||
|
system => true,
|
||||||
|
}
|
||||||
|
|
||||||
|
user { $user:
|
||||||
|
ensure => present,
|
||||||
|
system => true,
|
||||||
|
gid => $group,
|
||||||
|
home => $state_dir,
|
||||||
|
managehome => false,
|
||||||
|
shell => '/usr/sbin/nologin',
|
||||||
|
require => Group[$group],
|
||||||
|
}
|
||||||
|
|
||||||
|
file { [$install_dir, $state_dir, $log_dir, '/etc/flowercore']:
|
||||||
|
ensure => directory,
|
||||||
|
owner => $user,
|
||||||
|
group => $group,
|
||||||
|
mode => '0755',
|
||||||
|
}
|
||||||
|
|
||||||
|
file { '/etc/flowercore/divoom-tv.env':
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
content => "FC_DIVOOM_TV_PRESENTATION_MODE=${presentation_mode}\nFC_DIVOOM_TV_START_SCENE=${startup_scene}\nFC_DIVOOM_TV_REDUCED_MOTION=${reduced_motion}\n",
|
||||||
|
require => File['/etc/flowercore'],
|
||||||
|
}
|
||||||
|
|
||||||
|
$script_map = {
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-prelaunch.sh' => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-prelaunch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-launch.sh' => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-launch.sh',
|
||||||
|
'/usr/local/bin/flowercore-divoom-tv-hdmi-respond.sh' => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-hdmi-respond.sh',
|
||||||
|
}
|
||||||
|
|
||||||
|
$script_map.each |$dest, $src| {
|
||||||
|
file { $dest:
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0755',
|
||||||
|
source => "puppet:///modules/${src}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit_map = {
|
||||||
|
"/etc/systemd/system/${service_name}.service" => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv.service',
|
||||||
|
"/etc/systemd/system/${service_name}-hdmi.service" => 'profile/pi/fc_divoom_tv/flowercore-divoom-tv-hdmi.service',
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit_map.each |$dest, $src| {
|
||||||
|
file { $dest:
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
source => "puppet:///modules/${src}",
|
||||||
|
notify => Exec['fc-divoom-tv-systemd-reload'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file { '/etc/udev/rules.d/99-flowercore-divoom-tv-hdmi.rules':
|
||||||
|
ensure => file,
|
||||||
|
owner => 'root',
|
||||||
|
group => 'root',
|
||||||
|
mode => '0644',
|
||||||
|
source => 'puppet:///modules/profile/pi/fc_divoom_tv/99-flowercore-divoom-tv-hdmi.rules',
|
||||||
|
notify => Exec['fc-divoom-tv-udev-reload'],
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-tv-systemd-reload':
|
||||||
|
command => '/usr/bin/systemctl daemon-reload',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
|
||||||
|
exec { 'fc-divoom-tv-udev-reload':
|
||||||
|
command => '/usr/bin/udevadm control --reload-rules',
|
||||||
|
refreshonly => true,
|
||||||
|
path => ['/usr/bin', '/bin'],
|
||||||
|
}
|
||||||
|
|
||||||
|
service { $service_name:
|
||||||
|
ensure => $service_ensure,
|
||||||
|
enable => $service_enabled,
|
||||||
|
require => [
|
||||||
|
File["/etc/systemd/system/${service_name}.service"],
|
||||||
|
File['/etc/flowercore/divoom-tv.env'],
|
||||||
|
File['/usr/local/bin/flowercore-divoom-tv-prelaunch.sh'],
|
||||||
|
File['/usr/local/bin/flowercore-divoom-tv-launch.sh'],
|
||||||
|
Exec['fc-divoom-tv-systemd-reload'],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
systemctl restart flowercore-divoom-tv.service
|
||||||
25
apps/fc-divoom-tv-pi/scripts/flowercore-divoom-tv-launch.sh
Normal file
25
apps/fc-divoom-tv-pi/scripts/flowercore-divoom-tv-launch.sh
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP_BIN="${FC_DIVOOM_TV_BIN:-/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv}"
|
||||||
|
STATE_DIR="${FC_DIVOOM_TV_STATE_DIR:-/var/lib/fc-divoom-tv}"
|
||||||
|
LOG_DIR="${FC_DIVOOM_TV_LOG_DIR:-/var/log/fc-divoom-tv}"
|
||||||
|
PRESENTATION_MODE="${FC_DIVOOM_TV_PRESENTATION_MODE:-PillarboxSquare}"
|
||||||
|
START_SCENE="${FC_DIVOOM_TV_START_SCENE:-bluejay-clock}"
|
||||||
|
REDUCED_MOTION="${FC_DIVOOM_TV_REDUCED_MOTION:-false}"
|
||||||
|
|
||||||
|
COMMON_ARGS=(
|
||||||
|
"--target=hdmi"
|
||||||
|
"--presentation-mode=${PRESENTATION_MODE}"
|
||||||
|
"--startup-scene=${START_SCENE}"
|
||||||
|
"--reduced-motion=${REDUCED_MOTION}"
|
||||||
|
"--state-dir=${STATE_DIR}"
|
||||||
|
"--log-dir=${LOG_DIR}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if command -v cage >/dev/null 2>&1; then
|
||||||
|
exec cage -- "${APP_BIN}" "${COMMON_ARGS[@]}" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[$(date -Is)] cage not found; launching FlowerCore.Divoom.Tv directly" >&2
|
||||||
|
exec "${APP_BIN}" "${COMMON_ARGS[@]}" "$@"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP_BIN="${FC_DIVOOM_TV_BIN:-/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv}"
|
||||||
|
STATE_DIR="${FC_DIVOOM_TV_STATE_DIR:-/var/lib/fc-divoom-tv}"
|
||||||
|
LOG_DIR="${FC_DIVOOM_TV_LOG_DIR:-/var/log/fc-divoom-tv}"
|
||||||
|
|
||||||
|
mkdir -p "${STATE_DIR}" "${LOG_DIR}"
|
||||||
|
|
||||||
|
if [[ ! -x "${APP_BIN}" ]]; then
|
||||||
|
echo "[$(date -Is)] missing executable ${APP_BIN}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d /sys/class/drm ]] && ! find /sys/class/drm -maxdepth 1 -name 'card*-HDMI-A-*' -print -quit | grep -q .; then
|
||||||
|
echo "[$(date -Is)] no HDMI connector visible yet; continuing so the app can wait for display" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v cage >/dev/null 2>&1; then
|
||||||
|
echo "[$(date -Is)] cage available for fullscreen Wayland launch"
|
||||||
|
else
|
||||||
|
echo "[$(date -Is)] cage not installed; direct launch fallback will be used" >&2
|
||||||
|
fi
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Settle DRM for 2s before restarting the fullscreen Avalonia renderer.
|
||||||
|
SUBSYSTEM=="drm", KERNEL=="card?-HDMI-A-?", ACTION=="change", RUN+="/usr/bin/systemctl start flowercore-divoom-tv-hdmi.service"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Divoom TV HDMI hotplug responder
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/flowercore-divoom-tv-hdmi-respond.sh
|
||||||
40
apps/fc-divoom-tv-pi/systemd/flowercore-divoom-tv.service
Normal file
40
apps/fc-divoom-tv-pi/systemd/flowercore-divoom-tv.service
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Divoom TV HDMI Renderer (Avalonia fullscreen)
|
||||||
|
Documentation=https://github.com/astoltz/FlowerCore.Notes/blob/master/docs/standards/divoom-tv-hdmi-multitarget-render-substrate.md
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target systemd-user-sessions.service
|
||||||
|
ConditionPathExists=/opt/flowercore/divoom-tv/FlowerCore.Divoom.Tv
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=fc-divoom-tv
|
||||||
|
Group=fc-divoom-tv
|
||||||
|
WorkingDirectory=/opt/flowercore/divoom-tv
|
||||||
|
EnvironmentFile=-/etc/flowercore/divoom-tv.env
|
||||||
|
Environment=DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
|
Environment=XDG_RUNTIME_DIR=/run/fc-divoom-tv
|
||||||
|
RuntimeDirectory=fc-divoom-tv
|
||||||
|
RuntimeDirectoryMode=0700
|
||||||
|
ExecStartPre=/usr/local/bin/flowercore-divoom-tv-prelaunch.sh
|
||||||
|
ExecStart=/usr/local/bin/flowercore-divoom-tv-launch.sh
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10s
|
||||||
|
StartLimitBurst=5
|
||||||
|
StartLimitIntervalSec=300s
|
||||||
|
MemoryMax=2G
|
||||||
|
MemoryHigh=1500M
|
||||||
|
PrivateTmp=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/lib/fc-divoom-tv /var/log/fc-divoom-tv /run/fc-divoom-tv
|
||||||
|
TTYPath=/dev/tty1
|
||||||
|
StandardInput=tty
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
TTYReset=yes
|
||||||
|
TTYVHangup=yes
|
||||||
|
TTYVTDisallocate=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical.target
|
||||||
@@ -30,3 +30,26 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: dms-web-tls
|
secretName: dms-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose dms-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: dms-web-public
|
||||||
|
# namespace: fc-dms
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`dms.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: dms-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: dms-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
481
apps/fc-dns/fc-dns.yaml
Normal file
481
apps/fc-dns/fc-dns.yaml
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-dns
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
---
|
||||||
|
# 1Password-backed Secret for the pfSense admin password.
|
||||||
|
# The operator watches this CRD, resolves the vault item, and produces a
|
||||||
|
# K8s Secret of the same name with each 1P field as a key. The `password`
|
||||||
|
# field of the "pfSense Admin" item becomes Secret key `password`.
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: pfsense-admin
|
||||||
|
namespace: fc-dns
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/pfSense Admin"
|
||||||
|
---
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: dns-oidc-client
|
||||||
|
namespace: fc-dns
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/dns-oidc-client"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: dns-web-data
|
||||||
|
namespace: fc-dns
|
||||||
|
spec:
|
||||||
|
accessModes: [ReadWriteOnce]
|
||||||
|
storageClassName: longhorn
|
||||||
|
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": false,
|
||||||
|
"DefaultTenantHosts": [
|
||||||
|
"dns.iamworkin.lan"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Audit": {
|
||||||
|
"HashChain": {
|
||||||
|
"BridgeSensitivity": {
|
||||||
|
"Distribution": "Warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: dns-web
|
||||||
|
namespace: fc-dns
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: dns-web
|
||||||
|
app.kubernetes.io/managed-by: flowercore
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: dns-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: dns-web
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "5320"
|
||||||
|
prometheus.io/path: "/metrics/prometheus"
|
||||||
|
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: dns-web
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
fsGroup: 1654
|
||||||
|
containers:
|
||||||
|
- name: dns-web
|
||||||
|
image: localhost/fc-dns-web:v20260604-oidc-proper
|
||||||
|
imagePullPolicy: Never
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
ports:
|
||||||
|
- containerPort: 5320
|
||||||
|
env:
|
||||||
|
# pfSense admin password resolved by the 1Password operator.
|
||||||
|
# `FallbackPassword` is the Slice A seam exposed by
|
||||||
|
# OptionsFallbackPasswordResolver; Slice B will replace it with
|
||||||
|
# a pull-at-runtime 1P Connect resolver once Shared.Vault ships.
|
||||||
|
- 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__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.kubernetes.io/name: dns-web
|
||||||
|
ports:
|
||||||
|
- port: 5320
|
||||||
|
targetPort: 5320
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
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: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: dns-web-cert
|
||||||
|
namespace: fc-dns
|
||||||
|
spec:
|
||||||
|
secretName: dns-web-tls
|
||||||
|
issuerRef:
|
||||||
|
name: step-ca-dns01
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- dns.iamworkin.lan
|
||||||
|
duration: 720h
|
||||||
|
renewBefore: 240h
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: dns-web
|
||||||
|
namespace: fc-dns
|
||||||
|
spec:
|
||||||
|
entryPoints: [websecure]
|
||||||
|
routes:
|
||||||
|
- match: Host(`dns.iamworkin.lan`)
|
||||||
|
kind: Rule
|
||||||
|
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.kubernetes.io/name: dns-acme-webhook
|
||||||
|
app.kubernetes.io/managed-by: flowercore
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: dns-acme-webhook
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: dns-acme-webhook
|
||||||
|
spec:
|
||||||
|
serviceAccountName: dns-acme-webhook
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 1654
|
||||||
|
runAsGroup: 1654
|
||||||
|
fsGroup: 1654
|
||||||
|
containers:
|
||||||
|
- name: dns-acme-webhook
|
||||||
|
image: localhost/fc-dns-acme-webhook:v202604290845
|
||||||
|
imagePullPolicy: Never
|
||||||
|
securityContext:
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: [ALL]
|
||||||
|
ports:
|
||||||
|
- containerPort: 9443
|
||||||
|
name: https
|
||||||
|
env:
|
||||||
|
- name: ASPNETCORE_URLS
|
||||||
|
value: https://+:9443
|
||||||
|
- 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__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.kubernetes.io/name: 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
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: step-ca-dns01
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ4RENDQVdxZ0F3SUJBZ0lSQVBZMzU3RzZvdzZ6TUFMNSs0YlMya2t3Q2dZSUtvWkl6ajBFQXdJd1FERWEKTUJnR0ExVUVDaE1SU1VGdFYyOXlhMmx1SUVGRFRVVWdRMEV4SWpBZ0JnTlZCQU1UR1VsQmJWZHZjbXRwYmlCQgpRMDFGSUVOQklGSnZiM1FnUTBFd0hoY05Nall3TXpBNE1UZ3dOekV4V2hjTk16WXdNekExTVRnd056RXhXakJBCk1Sb3dHQVlEVlFRS0V4RkpRVzFYYjNKcmFXNGdRVU5OUlNCRFFURWlNQ0FHQTFVRUF4TVpTVUZ0VjI5eWEybHUKSUVGRFRVVWdRMEVnVW05dmRDQkRRVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSjJuMDRYMQpKWm81WmRxL2kxSWR2OCtmcXdaeUF6Qmg3d2hicWowU1dzSkw4VVdSYWJDTXFZQ3M3K2RYTzB4UlN6cWt3RkRMCngrdm9vT2FpOFJnUk5oYWpSVEJETUE0R0ExVWREd0VCL3dRRUF3SUJCakFTQmdOVkhSTUJBZjhFQ0RBR0FRSC8KQWdFQk1CMEdBMVVkRGdRV0JCUm51UFBRUjZpTS9INnZPbHVpVTNTeWdheXo4akFLQmdncWhrak9QUVFEQWdOSQpBREJGQWlFQXJRSzlkWVBHbUFac2RZbmp6aXVGVlZFNU5LWlVjY2VZdkdmR0MrdExYVXNDSUF1ZEYyekpyQ1JxCjNtSzUwWlpFVC9md1RrSndpRUY0ODI0bWpQOHAxQ0tNCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: step-ca-dns01-account-key
|
||||||
|
server: https://10.0.56.10:9443/acme/acme/directory
|
||||||
|
solvers:
|
||||||
|
- dns01:
|
||||||
|
webhook:
|
||||||
|
groupName: acme.flowercore.io
|
||||||
|
solverName: flowercore-dns
|
||||||
6
apps/fc-dns/kustomization.yaml
Normal file
6
apps/fc-dns/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# ArgoCD's bluejay-infra ApplicationSet discovers apps/* directories on main.
|
||||||
|
# The kustomization is included for local previews and single-app validation.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- fc-dns.yaml
|
||||||
195
apps/fc-library/fc-library.yaml
Normal file
195
apps/fc-library/fc-library.yaml
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# FlowerCore.Library.Web GitOps adoption manifest.
|
||||||
|
#
|
||||||
|
# Authored from the already-live fc-library resources on 2026-06-04.
|
||||||
|
# Keep the live image tag, Service ClusterIP, and PVC volumeName unchanged so
|
||||||
|
# ArgoCD adopts in place instead of replacing the workload or data volume.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: library-web-data
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
volumeName: pvc-2690bae2-4ee0-417a-b95f-50ec5c632b63
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: library-web
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-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:
|
||||||
|
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||||
|
- envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: library-web-config
|
||||||
|
image: localhost/fc-library-web:v20260602-library-owned-deploy-fix1
|
||||||
|
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
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: library-web
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.43.179.63
|
||||||
|
clusterIPs:
|
||||||
|
- 10.43.179.63
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: library-web-tls
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web-tls
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
dnsNames:
|
||||||
|
- library.iamworkin.lan
|
||||||
|
issuerRef:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: step-ca-acme
|
||||||
|
secretName: library-web-tls
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: library-web
|
||||||
|
namespace: fc-library
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: library-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-library
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`library.iamworkin.lan`)
|
||||||
|
services:
|
||||||
|
- name: library-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: library-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose library-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: library-web-public
|
||||||
|
# namespace: fc-library
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`library.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: library-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: library-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
@@ -83,6 +83,8 @@ spec:
|
|||||||
app.kubernetes.io/name: fc-llm-bridge
|
app.kubernetes.io/name: fc-llm-bridge
|
||||||
app.kubernetes.io/part-of: flowercore
|
app.kubernetes.io/part-of: flowercore
|
||||||
annotations:
|
annotations:
|
||||||
|
fc.flowercore.io/healthz-anon: "true"
|
||||||
|
fc.flowercore.io/probe-path: "/healthz"
|
||||||
prometheus.io/scrape: "true"
|
prometheus.io/scrape: "true"
|
||||||
prometheus.io/port: "8080"
|
prometheus.io/port: "8080"
|
||||||
prometheus.io/path: "/metrics"
|
prometheus.io/path: "/metrics"
|
||||||
@@ -116,6 +118,7 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
name: http
|
name: http
|
||||||
|
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||||
env:
|
env:
|
||||||
- name: ASPNETCORE_URLS
|
- name: ASPNETCORE_URLS
|
||||||
value: "http://+:8080"
|
value: "http://+:8080"
|
||||||
@@ -281,3 +284,26 @@ spec:
|
|||||||
port: 8080
|
port: 8080
|
||||||
tls:
|
tls:
|
||||||
secretName: fc-llm-bridge-tls
|
secretName: fc-llm-bridge-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose fc-llm-bridge publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: fc-llm-bridge-public
|
||||||
|
# namespace: fc-llm-bridge
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`llm-bridge.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: fc-llm-bridge-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: fc-llm-bridge
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
296
apps/fc-media/fc-media.yaml
Normal file
296
apps/fc-media/fc-media.yaml
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: fc-media
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-media
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
---
|
||||||
|
apiVersion: onepassword.com/v1
|
||||||
|
kind: OnePasswordItem
|
||||||
|
metadata:
|
||||||
|
name: media-oidc-client
|
||||||
|
namespace: fc-media
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
itemPath: "vaults/IAmWorkin/items/media-oidc-client"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: fc-media-config
|
||||||
|
namespace: fc-media
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
data:
|
||||||
|
appsettings.Production.json: |
|
||||||
|
{
|
||||||
|
"DatabaseProvider": "Sqlite",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Sqlite": "Data Source=/data/media.db"
|
||||||
|
},
|
||||||
|
"FlowerCore": {
|
||||||
|
"Auth": {
|
||||||
|
"Enabled": true,
|
||||||
|
"Oidc": {
|
||||||
|
"Authority": "https://id.iamworkin.lan/application/o/media/",
|
||||||
|
"ClientId": "media",
|
||||||
|
"ClientSecret": "",
|
||||||
|
"Audience": "media",
|
||||||
|
"RequireHttpsMetadata": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Tenant": {
|
||||||
|
"JwtClaimsEnabled": false,
|
||||||
|
"DefaultTenantHosts": [ "media.iamworkin.lan" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Media": {
|
||||||
|
"LibraryRoot": "/media/library",
|
||||||
|
"Sources": [
|
||||||
|
{
|
||||||
|
"Name": "BlueJayNAS Video",
|
||||||
|
"Driver": "Nfs",
|
||||||
|
"MountedPath": "/media/library",
|
||||||
|
"RemotePath": "nfs://10.0.58.3/volume1/video",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Notes": "Synology NFS media share mounted read-only inside the cluster."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"GeneratedRoot": "/data/generated",
|
||||||
|
"TranscodeRoot": "/data/transcodes",
|
||||||
|
"InboxPath": "/media/inbox",
|
||||||
|
"InboxScanIntervalMinutes": 5,
|
||||||
|
"ScanOnStartup": false,
|
||||||
|
"ComputeChecksums": false,
|
||||||
|
"FfmpegCommand": "ffmpeg",
|
||||||
|
"FfprobeCommand": "ffprobe",
|
||||||
|
"Hls": {
|
||||||
|
"MaxConcurrentJobs": 1
|
||||||
|
},
|
||||||
|
"DefaultViewerName": "BlueJay",
|
||||||
|
"Dlna": {
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MulticastAddress": "239.255.255.250",
|
||||||
|
"Port": 1900,
|
||||||
|
"DiscoveryTimeoutSeconds": 2,
|
||||||
|
"DescriptionFetchTimeoutSeconds": 2,
|
||||||
|
"MaxResponsesPerSearchTarget": 32,
|
||||||
|
"SearchTargets": [
|
||||||
|
"urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||||
|
"urn:schemas-upnp-org:device:MediaServer:1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: fc-media-data
|
||||||
|
namespace: fc-media
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: fc-media-web
|
||||||
|
namespace: fc-media
|
||||||
|
labels:
|
||||||
|
app: fc-media-web
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: fc-media-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: fc-media-web
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "5200"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
|
flowercore.io/healthz-auth-policy: "allow-anonymous"
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: rke2-server
|
||||||
|
containers:
|
||||||
|
- name: fc-media-web
|
||||||
|
image: localhost/fc-media-web:v20260604-oidc-proper
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- containerPort: 5200
|
||||||
|
name: http
|
||||||
|
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:
|
||||||
|
name: media-oidc-client
|
||||||
|
key: client_id
|
||||||
|
optional: true
|
||||||
|
- name: FlowerCore__Auth__Oidc__ClientSecret
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: media-oidc-client
|
||||||
|
key: client_secret
|
||||||
|
optional: true
|
||||||
|
- name: FlowerCore__Auth__Oidc__Authority
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: media-oidc-client
|
||||||
|
key: issuer_url
|
||||||
|
optional: true
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: "4"
|
||||||
|
memory: 4Gi
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /app/appsettings.Production.json
|
||||||
|
subPath: appsettings.Production.json
|
||||||
|
readOnly: true
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
- name: transcodes
|
||||||
|
mountPath: /data/transcodes
|
||||||
|
- name: media-library
|
||||||
|
mountPath: /media/library
|
||||||
|
readOnly: true
|
||||||
|
- name: media-inbox
|
||||||
|
mountPath: /media/inbox
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 5200
|
||||||
|
httpHeaders:
|
||||||
|
- name: X-Forwarded-Proto
|
||||||
|
value: https
|
||||||
|
failureThreshold: 18
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 5200
|
||||||
|
httpHeaders:
|
||||||
|
- name: X-Forwarded-Proto
|
||||||
|
value: https
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 5200
|
||||||
|
httpHeaders:
|
||||||
|
- name: X-Forwarded-Proto
|
||||||
|
value: https
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: fc-media-config
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: fc-media-data
|
||||||
|
- name: transcodes
|
||||||
|
nfs:
|
||||||
|
server: 10.0.58.3
|
||||||
|
path: /volume1/kubernetes/fc-media-transcodes
|
||||||
|
- name: media-inbox
|
||||||
|
nfs:
|
||||||
|
server: 10.0.58.3
|
||||||
|
path: /volume1/kubernetes/fc-media-inbox
|
||||||
|
- name: media-library
|
||||||
|
nfs:
|
||||||
|
server: 10.0.58.3
|
||||||
|
path: /volume1/video
|
||||||
|
readOnly: true
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: fc-media-web
|
||||||
|
namespace: fc-media
|
||||||
|
labels:
|
||||||
|
app: fc-media-web
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: fc-media-web
|
||||||
|
ports:
|
||||||
|
- port: 5200
|
||||||
|
targetPort: 5200
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: fc-media-tls
|
||||||
|
namespace: fc-media
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
secretName: fc-media-tls
|
||||||
|
issuerRef:
|
||||||
|
name: step-ca-acme
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- media.iamworkin.lan
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: fc-media-web
|
||||||
|
namespace: fc-media
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-media-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`media.iamworkin.lan`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: fc-media-web
|
||||||
|
port: 5200
|
||||||
|
tls:
|
||||||
|
secretName: fc-media-tls
|
||||||
6
apps/fc-media/kustomization.yaml
Normal file
6
apps/fc-media/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# ArgoCD's bluejay-infra ApplicationSet discovers apps/* directories on main.
|
||||||
|
# The kustomization is included for local previews and single-app validation.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- fc-media.yaml
|
||||||
@@ -30,3 +30,26 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: menuboard-web-tls
|
secretName: menuboard-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose menuboard-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: menuboard-web-public
|
||||||
|
# namespace: fc-menuboard
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`menuboard.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: menuboard-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: menuboard-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: messageboard-web
|
app: messageboard-web
|
||||||
annotations:
|
annotations:
|
||||||
|
fc.flowercore.io/healthz-anon: "true"
|
||||||
|
fc.flowercore.io/probe-path: "/healthz"
|
||||||
prometheus.io/scrape: "true"
|
prometheus.io/scrape: "true"
|
||||||
prometheus.io/port: "8080"
|
prometheus.io/port: "8080"
|
||||||
prometheus.io/path: "/metrics/prometheus"
|
prometheus.io/path: "/metrics/prometheus"
|
||||||
@@ -52,6 +54,7 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
name: http
|
name: http
|
||||||
|
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: messageboard-web-config
|
name: messageboard-web-config
|
||||||
@@ -141,3 +144,26 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: messageboard-web-tls
|
secretName: messageboard-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose messageboard-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: messageboard-web-public
|
||||||
|
# namespace: fc-messageboard
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`messageboard.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: messageboard-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: messageboard-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
@@ -30,3 +30,26 @@ spec:
|
|||||||
port: 5300
|
port: 5300
|
||||||
tls:
|
tls:
|
||||||
secretName: mysql-web-tls
|
secretName: mysql-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose mysql-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: mysql-web-public
|
||||||
|
# namespace: fc-mysql
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`mysql.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: mysql-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: mysql-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
@@ -30,3 +30,26 @@ spec:
|
|||||||
port: 5400
|
port: 5400
|
||||||
tls:
|
tls:
|
||||||
secretName: php-web-tls
|
secretName: php-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose php-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: php-web-public
|
||||||
|
# namespace: fc-php
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`php.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: php-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: php-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
@@ -30,3 +30,26 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: presentations-web-tls
|
secretName: presentations-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose presentations-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: presentations-web-public
|
||||||
|
# namespace: fc-presentations
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`presentations.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: presentations-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: presentations-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
196
apps/fc-retail/fc-retail.yaml
Normal file
196
apps/fc-retail/fc-retail.yaml
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# FlowerCore.Retail.Web GitOps adoption manifest.
|
||||||
|
#
|
||||||
|
# Authored from the already-live fc-retail resources on 2026-06-04.
|
||||||
|
# Keep the live image tag, Service ClusterIP, and PVC volumeName unchanged so
|
||||||
|
# ArgoCD adopts in place instead of replacing the workload or data volume.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: retail-web-data
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
storageClassName: longhorn
|
||||||
|
volumeMode: Filesystem
|
||||||
|
volumeName: pvc-3d40b336-eab4-41b3-812c-d5e9413ce0ab
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: retail-web
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-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:
|
||||||
|
# fc-safe-to-expose: X-Forwarded-Proto handled by AddFlowerCoreWebAuth (ADR-178) before any future public/OIDC flip.
|
||||||
|
- envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: retail-web-config
|
||||||
|
image: localhost/fc-retail-web:v20260602-retail-owned-deploy-fix5
|
||||||
|
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
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: retail-web
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
clusterIP: 10.43.239.8
|
||||||
|
clusterIPs:
|
||||||
|
- 10.43.239.8
|
||||||
|
internalTrafficPolicy: Cluster
|
||||||
|
ipFamilies:
|
||||||
|
- IPv4
|
||||||
|
ipFamilyPolicy: SingleStack
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 5000
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
sessionAffinity: None
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: retail-web-tls
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web-tls
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
dnsNames:
|
||||||
|
- retail.iamworkin.lan
|
||||||
|
issuerRef:
|
||||||
|
kind: ClusterIssuer
|
||||||
|
name: step-ca-acme
|
||||||
|
secretName: retail-web-tls
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: retail-web
|
||||||
|
namespace: fc-retail
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: retail-web
|
||||||
|
app.kubernetes.io/part-of: flowercore
|
||||||
|
app.kubernetes.io/managed-by: argocd
|
||||||
|
argocd.argoproj.io/instance: infra-fc-retail
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`retail.iamworkin.lan`)
|
||||||
|
services:
|
||||||
|
- name: retail-web
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: retail-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose retail-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: retail-web-public
|
||||||
|
# namespace: fc-retail
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`retail.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: retail-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: retail-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
@@ -30,3 +30,26 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: scoreboard-web-tls
|
secretName: scoreboard-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose scoreboard-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: scoreboard-web-public
|
||||||
|
# namespace: fc-scoreboard
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`scoreboard.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: scoreboard-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: scoreboard-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
@@ -37,3 +37,26 @@ spec:
|
|||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: segmentdisplay-web-tls
|
secretName: segmentdisplay-web-tls
|
||||||
|
# ---- PUBLIC HOST PRE-STAGING (DISABLED - Sprint 61+ exposure go-decision only) ----
|
||||||
|
# When the operator decides to expose segmentdisplay-web publicly, uncomment + update the host,
|
||||||
|
# then verify the five safe-to-expose gates (authentik-safe-to-expose-readiness-2026-06-07.md section 2).
|
||||||
|
#
|
||||||
|
# --- IngressRoute ---
|
||||||
|
# apiVersion: traefik.io/v1alpha1
|
||||||
|
# kind: IngressRoute
|
||||||
|
# metadata:
|
||||||
|
# name: segmentdisplay-web-public
|
||||||
|
# namespace: fc-segmentdisplay
|
||||||
|
# spec:
|
||||||
|
# entryPoints: [websecure]
|
||||||
|
# routes:
|
||||||
|
# - match: Host(`segmentdisplay.flowercore.io`) && (Method(`GET`) || Method(`HEAD`))
|
||||||
|
# kind: Rule
|
||||||
|
# middlewares:
|
||||||
|
# - name: segmentdisplay-web-public-profile-header # injects entitlement profile
|
||||||
|
# services:
|
||||||
|
# - name: segmentdisplay-web
|
||||||
|
# port: 80
|
||||||
|
# tls: {}
|
||||||
|
# # POST/PUT/PATCH/DELETE miss every route -> Traefik 404 -> no admin writes on the public surface.
|
||||||
|
# # Reference pattern: dist.flowercore.io (already live + method-gated; do not edit that one).
|
||||||
|
|||||||
14
apps/fc-signage-appletv/README.md
Normal file
14
apps/fc-signage-appletv/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# fc-signage-appletv
|
||||||
|
|
||||||
|
Apple TV signage is a sealed appliance running the `FlowerCore.Signage.Agent.AppleTv` tvOS app per ADR-134.
|
||||||
|
|
||||||
|
This ApplicationSet entry is documentation and inventory metadata only. It intentionally creates no `Deployment`, `Service`, or `Pod`.
|
||||||
|
|
||||||
|
The Apple TV app connects outbound to existing FC.Signage.Web surfaces:
|
||||||
|
|
||||||
|
- `https://signage.iamworkin.lan/hub/signage` for SignalR live status.
|
||||||
|
- `GET /api/v1/nodes/{nodeId}/state` for the 30 second polling fallback.
|
||||||
|
- `POST /api/v1/nodes/register` and `POST /api/v1/nodes/{nodeId}/enroll` for pairing and mTLS enrollment.
|
||||||
|
- `POST /api/v1/nodes/{nodeId}/heartbeat` for metrics, current content identity, and local audit excerpts.
|
||||||
|
|
||||||
|
Distribution is via Apple Developer Enterprise Program or TestFlight plus FC.Distribution / UpdateCenter publishing once Apple credentials are available.
|
||||||
5
apps/fc-signage-appletv/kustomization.yaml
Normal file
5
apps/fc-signage-appletv/kustomization.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- manifest.yaml
|
||||||
26
apps/fc-signage-appletv/manifest.yaml
Normal file
26
apps/fc-signage-appletv/manifest.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Apple TV signage is a sealed tvOS appliance. This ArgoCD app intentionally
|
||||||
|
# carries documentation metadata only; no Deployment, Service, or Pod resources
|
||||||
|
# are created for the player.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: fc-signage-appletv-docs
|
||||||
|
namespace: fc-signage
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: fc-signage-appletv
|
||||||
|
app.kubernetes.io/part-of: flowercore-signage
|
||||||
|
flowercore.io/manifest-kind: docs-only
|
||||||
|
data:
|
||||||
|
README: |
|
||||||
|
FlowerCore.Signage.Agent.AppleTv is distributed through Apple Developer
|
||||||
|
Enterprise Program or TestFlight, not Kubernetes.
|
||||||
|
|
||||||
|
The app connects outbound to FC.Signage.Web:
|
||||||
|
- SignalR: https://signage.iamworkin.lan/hub/signage
|
||||||
|
- Polling fallback: GET /api/v1/nodes/{nodeId}/state
|
||||||
|
- Enrollment: POST /api/v1/nodes/{nodeId}/enroll
|
||||||
|
- Heartbeat: POST /api/v1/nodes/{nodeId}/heartbeat
|
||||||
|
|
||||||
|
This placeholder gives ArgoCD and inventory dashboards a first-class
|
||||||
|
Apple TV signage app entry without creating runtime pods.
|
||||||
17
apps/fc-signage-pi-player/README.md
Normal file
17
apps/fc-signage-pi-player/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# FlowerCore Signage Pi Player
|
||||||
|
|
||||||
|
Phase 1 Raspberry Pi signage player packaging for Chromium kiosk deployments.
|
||||||
|
This bundle is intentionally air-gap friendly: systemd units, shell scripts,
|
||||||
|
udev rules, and Chromium managed policy are all checked into the repo and are
|
||||||
|
installed by `FlowerCore.Puppet`.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- Bootstrap a stable node identity and mTLS client certificate.
|
||||||
|
- Launch Chromium in kiosk mode against `FC.Signage.Web` player routes.
|
||||||
|
- Restart the kiosk on HDMI hotplug.
|
||||||
|
- Renew mTLS certificates daily when fewer than 30 days remain.
|
||||||
|
- Detect display capabilities at boot, daily, and on HDMI hotplug.
|
||||||
|
|
||||||
|
Phase 2 native Avalonia rendering is documented separately in Notes and remains
|
||||||
|
deferred.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"AutofillAddressEnabled": false,
|
||||||
|
"AutofillCreditCardEnabled": false,
|
||||||
|
"PasswordManagerEnabled": false,
|
||||||
|
"BrowserSignin": 0,
|
||||||
|
"MetricsReportingEnabled": false,
|
||||||
|
"SafeBrowsingProtectionLevel": 0,
|
||||||
|
"DefaultNotificationsSetting": 2,
|
||||||
|
"DefaultPopupsSetting": 2,
|
||||||
|
"BackgroundModeEnabled": false,
|
||||||
|
"DefaultBrowserSettingEnabled": false,
|
||||||
|
"PromotionalTabsEnabled": false,
|
||||||
|
"CommandLineFlagSecurityWarningsEnabled": false,
|
||||||
|
"ExtensionInstallBlocklist": ["*"]
|
||||||
|
}
|
||||||
132
apps/fc-signage-pi-player/scripts/fc-signage-detect-display
Normal file
132
apps/fc-signage-pi-player/scripts/fc-signage-detect-display
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
NODE_JSON="/etc/flowercore/signage-node.json"
|
||||||
|
CERT_DIR="/etc/fc-signage-player"
|
||||||
|
SIGNAGE_URL="${FC_SIGNAGE_URL:-https://signage.iamworkin.lan}"
|
||||||
|
NODE_ID=$(jq -r '.nodeId' "$NODE_JSON")
|
||||||
|
|
||||||
|
CONNECTORS=()
|
||||||
|
for dir in /sys/class/drm/card*-HDMI-A-*; do
|
||||||
|
[[ -e "$dir/status" ]] || continue
|
||||||
|
if [[ "$(cat "$dir/status")" == "connected" ]]; then
|
||||||
|
CONNECTORS+=("$(basename "$dir")")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#CONNECTORS[@]} -eq 0 ]]; then
|
||||||
|
CAPABILITIES_JSON=$(jq -n --arg id "$NODE_ID" '{
|
||||||
|
nodeId: $id,
|
||||||
|
platform: "linux-arm64-pi",
|
||||||
|
displayConnected: false,
|
||||||
|
detectedAt: (now | todate),
|
||||||
|
note: "No HDMI display detected"
|
||||||
|
}')
|
||||||
|
else
|
||||||
|
PRIMARY="${CONNECTORS[0]}"
|
||||||
|
EDID_PATH="/sys/class/drm/${PRIMARY}/edid"
|
||||||
|
WIDTH=0
|
||||||
|
HEIGHT=0
|
||||||
|
REFRESH=60
|
||||||
|
HDR=false
|
||||||
|
AUDIO_HDMI=false
|
||||||
|
MFG=""
|
||||||
|
MODEL=""
|
||||||
|
PHYSICAL_SIZE=null
|
||||||
|
|
||||||
|
if [[ -s "$EDID_PATH" ]] && command -v edid-decode >/dev/null 2>&1; then
|
||||||
|
EDID_INFO=$(edid-decode < "$EDID_PATH" 2>/dev/null || true)
|
||||||
|
MFG=$(echo "$EDID_INFO" | grep -m1 -oP 'Manufacturer:\s*\K\S+' || true)
|
||||||
|
MODEL=$(echo "$EDID_INFO" | grep -m1 -oP 'Model:\s*\K\S+' || true)
|
||||||
|
PREF=$(echo "$EDID_INFO" | grep -m1 -oP '\d+x\d+\s*@\s*\d+(?:\.\d+)?\s*Hz' || true)
|
||||||
|
if [[ -n "$PREF" ]]; then
|
||||||
|
WIDTH=$(echo "$PREF" | grep -oP '^\d+')
|
||||||
|
HEIGHT=$(echo "$PREF" | grep -oP 'x\K\d+')
|
||||||
|
REFRESH=$(echo "$PREF" | grep -oP '@\s*\K[\d.]+' | cut -d. -f1)
|
||||||
|
fi
|
||||||
|
if echo "$EDID_INFO" | grep -qiE 'HDR (Static|Dynamic) Metadata Block'; then HDR=true; fi
|
||||||
|
if echo "$EDID_INFO" | grep -qiE 'CEA Audio Block|Audio Format Descriptor'; then AUDIO_HDMI=true; fi
|
||||||
|
PH_W=$(echo "$EDID_INFO" | grep -m1 -oP 'Maximum image size:\s*\K\d+\s*cm\s*x\s*\d+' || true)
|
||||||
|
if [[ -n "$PH_W" ]]; then
|
||||||
|
PH_CM_W=$(echo "$PH_W" | grep -oP '^\d+')
|
||||||
|
PH_CM_H=$(echo "$PH_W" | grep -oP 'x\s*\K\d+')
|
||||||
|
if (( PH_CM_W > 0 && PH_CM_H > 0 )); then
|
||||||
|
PHYSICAL_SIZE=$(awk -v w="$PH_CM_W" -v h="$PH_CM_H" 'BEGIN { printf "%.1f", sqrt(w*w + h*h)/2.54 }')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$WIDTH" == "0" ]] && command -v kmsprint >/dev/null 2>&1; then
|
||||||
|
KMS=$(kmsprint 2>/dev/null | grep -A2 "$PRIMARY" | grep -oP '\d+x\d+' | head -1 || true)
|
||||||
|
if [[ -n "$KMS" ]]; then
|
||||||
|
WIDTH=$(echo "$KMS" | grep -oP '^\d+')
|
||||||
|
HEIGHT=$(echo "$KMS" | grep -oP 'x\K\d+')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
AUDIO_ALSA=false
|
||||||
|
if aplay -l 2>/dev/null | grep -qi 'card.*HDMI'; then AUDIO_ALSA=true; fi
|
||||||
|
HAS_AUDIO=false
|
||||||
|
if [[ "$AUDIO_HDMI" == "true" && "$AUDIO_ALSA" == "true" ]]; then HAS_AUDIO=true; fi
|
||||||
|
|
||||||
|
CAPABILITIES_JSON=$(jq -n \
|
||||||
|
--arg id "$NODE_ID" \
|
||||||
|
--argjson w "$WIDTH" \
|
||||||
|
--argjson h "$HEIGHT" \
|
||||||
|
--argjson r "$REFRESH" \
|
||||||
|
--argjson hdr "$HDR" \
|
||||||
|
--argjson audio "$HAS_AUDIO" \
|
||||||
|
--arg connector "$PRIMARY" \
|
||||||
|
--arg mfg "$MFG" \
|
||||||
|
--arg model "$MODEL" \
|
||||||
|
--argjson size "$PHYSICAL_SIZE" \
|
||||||
|
'{
|
||||||
|
nodeId: $id,
|
||||||
|
platform: "linux-arm64-pi",
|
||||||
|
displayConnected: true,
|
||||||
|
detectedAt: (now | todate),
|
||||||
|
hardware: {
|
||||||
|
maxResolution: { width: $w, height: $h },
|
||||||
|
nativeResolution: { width: $w, height: $h },
|
||||||
|
refreshRateHz: $r,
|
||||||
|
colorDepth: ($hdr | if . then "Color30Hdr" else "Color24" end),
|
||||||
|
hasAudioOutput: $audio,
|
||||||
|
audioChannelCount: ($audio | if . then 2 else 0 end),
|
||||||
|
physicalSizeInches: $size,
|
||||||
|
connector: $connector,
|
||||||
|
manufacturer: $mfg,
|
||||||
|
modelName: $model
|
||||||
|
},
|
||||||
|
render: { codecs: ["h264", "vp9", "mp4"] }
|
||||||
|
}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENDPOINT_CANDIDATES=(
|
||||||
|
"${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/capabilities"
|
||||||
|
"${SIGNAGE_URL}/api/v1/displays/${NODE_ID}/capability-profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
SUCCESS=false
|
||||||
|
for url in "${ENDPOINT_CANDIDATES[@]}"; do
|
||||||
|
HTTP_STATUS=$(curl -sk -o /tmp/cap-response.json -w "%{http_code}" \
|
||||||
|
--max-time 10 \
|
||||||
|
--cert "$CERT_DIR/client.crt" --key "$CERT_DIR/client.key" \
|
||||||
|
-X POST "$url" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$CAPABILITIES_JSON" || echo "000")
|
||||||
|
if [[ "$HTTP_STATUS" == "200" || "$HTTP_STATUS" == "201" || "$HTTP_STATUS" == "204" ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p /var/log/fc-signage-player
|
||||||
|
if [[ "$SUCCESS" != "true" ]]; then
|
||||||
|
echo "[$(date -Is)] capability declare: no endpoint accepted the profile; logging locally" \
|
||||||
|
| tee -a /var/log/fc-signage-player/capabilities.log
|
||||||
|
echo "$CAPABILITIES_JSON" | tee -a /var/log/fc-signage-player/capabilities.log
|
||||||
|
else
|
||||||
|
echo "[$(date -Is)] capability declare: ok ($url)" | tee -a /var/log/fc-signage-player/capabilities.log
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$CAPABILITIES_JSON"
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
NODE_JSON="/etc/flowercore/signage-node.json"
|
||||||
|
CERT_DIR="/etc/fc-signage-player"
|
||||||
|
SIGNAGE_URL="${FC_SIGNAGE_URL:-https://signage.iamworkin.lan}"
|
||||||
|
SETUP_CODE_FILE="/etc/flowercore/signage-setup-code"
|
||||||
|
|
||||||
|
mkdir -p /etc/flowercore "$CERT_DIR" /var/log/fc-signage-player
|
||||||
|
chown fc-signage:fc-signage /etc/flowercore "$CERT_DIR" /var/log/fc-signage-player
|
||||||
|
chmod 0750 "$CERT_DIR"
|
||||||
|
|
||||||
|
if [[ -s "$NODE_JSON" && -s "$CERT_DIR/client.p12" ]]; then
|
||||||
|
ENROLLED=$(jq -r '.enrolledAt // empty' "$NODE_JSON")
|
||||||
|
if [[ -n "$ENROLLED" ]]; then
|
||||||
|
echo "[$(date -Is)] bootstrap: already enrolled at $ENROLLED; skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -s "$NODE_JSON" ]]; then
|
||||||
|
NODE_UUID=$(jq -r '.nodeUuid // empty' "$NODE_JSON")
|
||||||
|
MACHINE_ID=$(jq -r '.machineId // empty' "$NODE_JSON")
|
||||||
|
else
|
||||||
|
NODE_UUID=$(uuidgen)
|
||||||
|
MACHINE_ID=$(echo "$NODE_UUID" | tr -d '-' | cut -c1-16)
|
||||||
|
jq -n --arg uuid "$NODE_UUID" --arg machine "$MACHINE_ID" --arg host "$(hostname -f)" --arg ts "$(date -Is)" \
|
||||||
|
'{nodeUuid: $uuid, machineId: $machine, hostname: $host, platform: "linux-arm64-pi", createdAt: $ts}' \
|
||||||
|
> "$NODE_JSON"
|
||||||
|
chmod 0640 "$NODE_JSON"
|
||||||
|
chown fc-signage:fc-signage "$NODE_JSON"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SETUP_CODE=""
|
||||||
|
if [[ -s "$SETUP_CODE_FILE" ]]; then
|
||||||
|
SETUP_CODE=$(tr -d '\r\n\t ' < "$SETUP_CODE_FILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODEL=$(tr -d '\0' < /sys/firmware/devicetree/base/model 2>/dev/null || echo Unknown)
|
||||||
|
REG_PAYLOAD=$(jq -n \
|
||||||
|
--arg machine "$MACHINE_ID" \
|
||||||
|
--arg name "$(hostname -f)" \
|
||||||
|
--arg setup "$SETUP_CODE" \
|
||||||
|
--arg resolution "1920x1080" \
|
||||||
|
--arg model "$MODEL" \
|
||||||
|
'{
|
||||||
|
machineId: $machine,
|
||||||
|
name: $name,
|
||||||
|
setupCode: ($setup | if . == "" then null else . end),
|
||||||
|
resolution: $resolution,
|
||||||
|
hardwareModel: $model,
|
||||||
|
platform: "linux-arm64-pi"
|
||||||
|
}')
|
||||||
|
|
||||||
|
for attempt in 1 2; do
|
||||||
|
HTTP_STATUS=$(curl -sk -o /tmp/register-response.json -w "%{http_code}" \
|
||||||
|
--max-time 15 \
|
||||||
|
-X POST "${SIGNAGE_URL}/api/v1/nodes/register" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$REG_PAYLOAD" || echo "000")
|
||||||
|
if [[ "$HTTP_STATUS" == "200" || "$HTTP_STATUS" == "201" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "[$(date -Is)] bootstrap: register attempt $attempt returned $HTTP_STATUS" >&2
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "201" ]]; then
|
||||||
|
echo "[$(date -Is)] bootstrap: register failed after 2 attempts" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_ID=$(jq -r '.nodeId // empty' /tmp/register-response.json)
|
||||||
|
if [[ -z "$NODE_ID" ]]; then
|
||||||
|
echo "[$(date -Is)] bootstrap: register response did not include nodeId" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
jq --arg id "$NODE_ID" '.nodeId = $id' "$NODE_JSON" > "${NODE_JSON}.tmp" && mv "${NODE_JSON}.tmp" "$NODE_JSON"
|
||||||
|
|
||||||
|
if [[ -s "$SETUP_CODE_FILE" ]]; then
|
||||||
|
curl -sk -X POST "${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/approve-via-setup-code" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"setupCode\":\"${SETUP_CODE}\"}" \
|
||||||
|
-o /dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
STATUS=""
|
||||||
|
DEADLINE=$(( $(date +%s) + 1800 ))
|
||||||
|
while (( $(date +%s) < DEADLINE )); do
|
||||||
|
STATUS=$(curl -sk --max-time 5 "${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/status" | jq -r '.status // empty')
|
||||||
|
if [[ "$STATUS" == "Approved" || "$STATUS" == "Enrolled" || "$STATUS" == "Online" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 15
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$STATUS" != "Approved" && "$STATUS" != "Enrolled" && "$STATUS" != "Online" ]]; then
|
||||||
|
echo "[$(date -Is)] bootstrap: approval not granted within 30min budget" >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
KEY_PATH="${CERT_DIR}/client.key"
|
||||||
|
CSR_PATH="${CERT_DIR}/client.csr"
|
||||||
|
openssl ecparam -genkey -name prime256v1 -out "$KEY_PATH"
|
||||||
|
openssl req -new -key "$KEY_PATH" -out "$CSR_PATH" \
|
||||||
|
-subj "/CN=${NODE_ID}/O=FlowerCore/OU=SignagePlayer-Pi"
|
||||||
|
|
||||||
|
ENROLL_PAYLOAD=$(jq -n --arg csr "$(cat "$CSR_PATH")" '{certificateSigningRequest: $csr}')
|
||||||
|
HTTP_STATUS=$(curl -sk -o /tmp/enroll-response.json -w "%{http_code}" \
|
||||||
|
--max-time 15 \
|
||||||
|
-X POST "${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/enroll" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$ENROLL_PAYLOAD")
|
||||||
|
|
||||||
|
if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "201" ]]; then
|
||||||
|
echo "[$(date -Is)] bootstrap: enroll failed with HTTP $HTTP_STATUS" >&2
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq -r '.clientCertificatePem // .signedCertificatePem' /tmp/enroll-response.json > "${CERT_DIR}/client.crt"
|
||||||
|
jq -r '.caCertificatePem' /tmp/enroll-response.json > "${CERT_DIR}/ca-chain.pem"
|
||||||
|
P12_PASS=$(openssl rand -hex 24)
|
||||||
|
echo -n "$P12_PASS" > "${CERT_DIR}/client.p12.pass"
|
||||||
|
chmod 0600 "${CERT_DIR}/client.p12.pass"
|
||||||
|
|
||||||
|
openssl pkcs12 -export \
|
||||||
|
-inkey "$KEY_PATH" \
|
||||||
|
-in "${CERT_DIR}/client.crt" \
|
||||||
|
-certfile "${CERT_DIR}/ca-chain.pem" \
|
||||||
|
-out "${CERT_DIR}/client.p12" \
|
||||||
|
-password "pass:${P12_PASS}"
|
||||||
|
|
||||||
|
chown fc-signage:fc-signage "${CERT_DIR}"/* "$NODE_JSON"
|
||||||
|
chmod 0640 "${CERT_DIR}/client.p12" "${CERT_DIR}/client.crt" "${CERT_DIR}/ca-chain.pem" "$KEY_PATH"
|
||||||
|
chmod 0600 "${CERT_DIR}/client.p12.pass"
|
||||||
|
|
||||||
|
EXPIRY=$(openssl x509 -in "${CERT_DIR}/client.crt" -enddate -noout | sed 's/notAfter=//')
|
||||||
|
jq --arg ts "$(date -Is)" --arg exp "$EXPIRY" \
|
||||||
|
'.enrolledAt = $ts | .certExpiry = $exp' "$NODE_JSON" > "${NODE_JSON}.tmp" \
|
||||||
|
&& mv "${NODE_JSON}.tmp" "$NODE_JSON"
|
||||||
|
|
||||||
|
systemctl start flowercore-signage-detect-display.service || true
|
||||||
|
systemctl start flowercore-signage-player-pi.service || true
|
||||||
|
echo "[$(date -Is)] bootstrap: enrolled and kiosk started (NodeId=${NODE_ID})"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
systemctl start flowercore-signage-detect-display.service || true
|
||||||
|
systemctl restart flowercore-signage-player-pi.service
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
NODE_JSON="/etc/flowercore/signage-node.json"
|
||||||
|
NODE_ID=$(jq -r '.nodeId' "$NODE_JSON")
|
||||||
|
SIGNAGE_URL="${FC_SIGNAGE_URL:-https://signage.iamworkin.lan}"
|
||||||
|
CERT_DIR="/etc/fc-signage-player"
|
||||||
|
|
||||||
|
CERT_THUMB=$(openssl pkcs12 -in "$CERT_DIR/client.p12" -passin file:"$CERT_DIR/client.p12.pass" -nodes -nokeys 2>/dev/null \
|
||||||
|
| openssl x509 -fingerprint -sha256 -noout \
|
||||||
|
| sed 's/.*=//' \
|
||||||
|
| tr -d ':')
|
||||||
|
|
||||||
|
PLAYER_URL="${SIGNAGE_URL}/player/${NODE_ID}/embed?token=${CERT_THUMB}"
|
||||||
|
HTTP_STATUS=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 5 \
|
||||||
|
--cert-type P12 --cert "$CERT_DIR/client.p12:$(cat "$CERT_DIR/client.p12.pass")" \
|
||||||
|
"$PLAYER_URL" || echo "000")
|
||||||
|
|
||||||
|
mkdir -p /var/log/fc-signage-player
|
||||||
|
if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "301" && "$HTTP_STATUS" != "302" ]]; then
|
||||||
|
echo "[$(date -Is)] /embed returned $HTTP_STATUS; falling back to /player/${NODE_ID}" \
|
||||||
|
>> /var/log/fc-signage-player/url-divergence.log
|
||||||
|
PLAYER_URL="${SIGNAGE_URL}/player/${NODE_ID}?token=${CERT_THUMB}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec chromium-browser \
|
||||||
|
--kiosk \
|
||||||
|
--noerrdialogs \
|
||||||
|
--disable-infobars \
|
||||||
|
--disable-translate \
|
||||||
|
--disable-features=TranslateUI,InfiniteSessionRestore \
|
||||||
|
--autoplay-policy=no-user-gesture-required \
|
||||||
|
--password-store=basic \
|
||||||
|
--user-data-dir=/var/lib/fc-signage-player/profile \
|
||||||
|
--disk-cache-dir=/var/lib/fc-signage-player/cache \
|
||||||
|
--disk-cache-size=104857600 \
|
||||||
|
--no-first-run \
|
||||||
|
--no-default-browser-check \
|
||||||
|
--check-for-update-interval=2592000 \
|
||||||
|
--enable-features=OverlayScrollbar \
|
||||||
|
--start-fullscreen \
|
||||||
|
--window-position=0,0 \
|
||||||
|
--window-size=1920,1080 \
|
||||||
|
"$PLAYER_URL"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
mkdir -p /var/log/fc-signage-player
|
||||||
|
|
||||||
|
for f in /etc/flowercore/signage-node.json /etc/fc-signage-player/client.p12 /etc/fc-signage-player/client.p12.pass; do
|
||||||
|
if [[ ! -r "$f" ]]; then
|
||||||
|
echo "[$(date -Is)] prelaunch: missing or unreadable $f" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if openssl pkcs12 -in /etc/fc-signage-player/client.p12 -passin file:/etc/fc-signage-player/client.p12.pass -nokeys -clcerts 2>/dev/null \
|
||||||
|
| openssl x509 -checkend $((7*24*3600)) -noout; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo "[$(date -Is)] prelaunch: client cert expires within 7 days" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[$(date -Is)] prelaunch: ok" | tee -a /var/log/fc-signage-player/prelaunch.log
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CERT_DIR="/etc/fc-signage-player"
|
||||||
|
NODE_JSON="/etc/flowercore/signage-node.json"
|
||||||
|
SIGNAGE_URL="${FC_SIGNAGE_URL:-https://signage.iamworkin.lan}"
|
||||||
|
|
||||||
|
[[ -s "$CERT_DIR/client.crt" ]] || { echo "no cert to renew"; exit 0; }
|
||||||
|
|
||||||
|
if openssl x509 -in "$CERT_DIR/client.crt" -checkend $((30*24*3600)) -noout; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
NODE_ID=$(jq -r '.nodeId' "$NODE_JSON")
|
||||||
|
NEW_KEY="$CERT_DIR/client.key.new"
|
||||||
|
NEW_CSR="$CERT_DIR/client.csr.new"
|
||||||
|
|
||||||
|
openssl ecparam -genkey -name prime256v1 -out "$NEW_KEY"
|
||||||
|
openssl req -new -key "$NEW_KEY" -out "$NEW_CSR" \
|
||||||
|
-subj "/CN=${NODE_ID}/O=FlowerCore/OU=SignagePlayer-Pi"
|
||||||
|
|
||||||
|
HTTP_STATUS=$(curl -sk -o /tmp/renew-response.json -w "%{http_code}" \
|
||||||
|
--cert "$CERT_DIR/client.crt" --key "$CERT_DIR/client.key" \
|
||||||
|
-X POST "${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/renew" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$(jq -n --arg csr "$(cat "$NEW_CSR")" '{certificateSigningRequest: $csr}')")
|
||||||
|
|
||||||
|
if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "201" ]]; then
|
||||||
|
echo "[$(date -Is)] renew: failed HTTP $HTTP_STATUS; leaving old cert in place" >&2
|
||||||
|
exit 5
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq -r '.clientCertificatePem // .signedCertificatePem' /tmp/renew-response.json > "$CERT_DIR/client.crt.new"
|
||||||
|
jq -r '.caCertificatePem' /tmp/renew-response.json > "$CERT_DIR/ca-chain.pem.new"
|
||||||
|
P12_PASS=$(cat "$CERT_DIR/client.p12.pass")
|
||||||
|
openssl pkcs12 -export -inkey "$NEW_KEY" -in "$CERT_DIR/client.crt.new" \
|
||||||
|
-certfile "$CERT_DIR/ca-chain.pem.new" \
|
||||||
|
-out "$CERT_DIR/client.p12.new" -password "pass:${P12_PASS}"
|
||||||
|
|
||||||
|
mv "$CERT_DIR/client.key.new" "$CERT_DIR/client.key"
|
||||||
|
mv "$CERT_DIR/client.crt.new" "$CERT_DIR/client.crt"
|
||||||
|
mv "$CERT_DIR/ca-chain.pem.new" "$CERT_DIR/ca-chain.pem"
|
||||||
|
mv "$CERT_DIR/client.p12.new" "$CERT_DIR/client.p12"
|
||||||
|
|
||||||
|
chown fc-signage:fc-signage "$CERT_DIR"/client.*
|
||||||
|
systemctl restart flowercore-signage-player-pi.service
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Settle DRM for 2s before restarting Chromium, then redeclare capabilities.
|
||||||
|
SUBSYSTEM=="drm", KERNEL=="card?-HDMI-A-?", ACTION=="change", RUN+="/usr/bin/systemctl start flowercore-signage-player-pi-hdmi.service"
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Signage Pi: first-boot identity + mTLS enrollment
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
Before=flowercore-signage-player-pi.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/flowercore-signage-bootstrap.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
TimeoutStartSec=2100
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Signage Pi: detect connected display + declare capabilities
|
||||||
|
After=flowercore-signage-bootstrap.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
User=fc-signage
|
||||||
|
ExecStart=/usr/local/bin/fc-signage-detect-display
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Daily FlowerCore Signage Pi display capability redeclaration
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=daily
|
||||||
|
RandomizedDelaySec=1h
|
||||||
|
Persistent=true
|
||||||
|
OnBootSec=30s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Signage Pi Player HDMI hotplug responder
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/flowercore-signage-hdmi-respond.sh
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Digital Signage Pi Player (Chromium kiosk)
|
||||||
|
Documentation=https://github.com/astoltz/FlowerCore.Notes/blob/master/docs/standards/appletv-pi-signage-agents-design.md
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target graphical.target
|
||||||
|
ConditionPathExists=/etc/flowercore/signage-node.json
|
||||||
|
ConditionPathExists=/etc/fc-signage-player/client.p12
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=fc-signage
|
||||||
|
Group=fc-signage
|
||||||
|
WorkingDirectory=/var/lib/fc-signage-player
|
||||||
|
EnvironmentFile=-/etc/flowercore/signage-player.env
|
||||||
|
ExecStartPre=/usr/local/bin/flowercore-signage-prelaunch.sh
|
||||||
|
ExecStart=/usr/local/bin/flowercore-signage-launch.sh
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10s
|
||||||
|
StartLimitBurst=5
|
||||||
|
StartLimitIntervalSec=300s
|
||||||
|
MemoryMax=2G
|
||||||
|
MemoryHigh=1500M
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths=/var/lib/fc-signage-player /var/log/fc-signage-player
|
||||||
|
PrivateTmp=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=graphical.target
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FlowerCore Signage Pi: cert renewal worker
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/flowercore-signage-renew-cert.sh
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user