From 2c0afc28e49700d1edba0ecca2117e3a2d17590b Mon Sep 17 00:00:00 2001 From: Andrew Stoltz Date: Wed, 3 Jun 2026 19:28:22 -0500 Subject: [PATCH] deploy(fc-library): add Library.Web internal-host deployment From-scratch .Web deploy at library.iamworkin.lan (operator-authorized 2026-06-03). Cloned from the worldbuilder pattern: Deployment + Service + Longhorn RWO PVC + step-ca cert + Traefik IngressRoute. SQLite at /data/library.db, no OIDC, both /health + /healthz probes. Image localhost/fc-library:v202606031925 imported to both RKE2 nodes. DNS library.iamworkin.lan -> 10.0.56.200 already in pfSense. Co-Authored-By: Claude Opus 4.8 --- apps/fc-library/library.yaml | 224 +++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 apps/fc-library/library.yaml diff --git a/apps/fc-library/library.yaml b/apps/fc-library/library.yaml new file mode 100644 index 0000000..db300d3 --- /dev/null +++ b/apps/fc-library/library.yaml @@ -0,0 +1,224 @@ +# FlowerCore.Library.Web — library circulation / catalog / OPAC service. +# +# Deployment + Service + PVC + Certificate + IngressRoute. ArgoCD-managed. +# Cloned from apps/worldbuilder/worldbuilder.yaml (proven from-scratch .Web pattern). +# +# Image build (BLUEJAY-WS): +# dotnet.exe publish src/FlowerCore.Library.Web -c Release -r linux-x64 --self-contained -o deploy/app -p:NoIncremental=true +# podman build -t localhost/fc-library:v -f deploy/Dockerfile.deploy deploy +# podman save localhost/fc-library:v -o /tmp/fc-library-v.tar +# for ip in 10.0.56.11 10.0.56.12; do # 2-node cluster (agent2 retired 2026-06-01) +# scp -o IdentitiesOnly=yes -i ~/.ssh/fcadmin_ed25519 /tmp/fc-library-v.tar fcadmin@$ip:/tmp/ +# ssh -o IdentitiesOnly=yes -i ~/.ssh/fcadmin_ed25519 fcadmin@$ip "sudo /var/lib/rancher/rke2/bin/ctr -a /run/k3s/containerd/containerd.sock -n k8s.io images import /tmp/fc-library-v.tar" +# done +# +# DNS preflight: library.iamworkin.lan -> 10.0.56.200 already resolves in pfSense Unbound. +--- +apiVersion: v1 +kind: Namespace +metadata: + name: fc-library + labels: + app.kubernetes.io/name: fc-library + app.kubernetes.io/part-of: flowercore + app.kubernetes.io/managed-by: argocd + flowercore.io/tenant-id: system + flowercore.io/created-by: bluejay-infra +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: library-data + namespace: fc-library + labels: + app.kubernetes.io/name: library-data + app.kubernetes.io/component: storage + app.kubernetes.io/part-of: flowercore + app.kubernetes.io/managed-by: argocd + flowercore.io/tenant-id: system + flowercore.io/created-by: bluejay-infra +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn + resources: + requests: + storage: 5Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: library-web + namespace: fc-library + labels: + app.kubernetes.io/name: library-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: 1 + revisionHistoryLimit: 3 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: library-web + template: + metadata: + labels: + app.kubernetes.io/name: library-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: + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/metrics/prometheus" + flowercore.io/audit-trace-id: "library-web-runtime" + spec: + securityContext: + fsGroup: 1654 + fsGroupChangePolicy: OnRootMismatch + containers: + - name: web + image: localhost/fc-library:v202606031925 + imagePullPolicy: Never + ports: + - containerPort: 8080 + name: http + env: + - name: ASPNETCORE_URLS + value: "http://+:8080" + - name: ASPNETCORE_ENVIRONMENT + value: "Production" + - name: DOTNET_RUNNING_IN_CONTAINER + value: "true" + - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT + value: "false" + - name: FlowerCore__Database__Provider + value: "Sqlite" + - name: FlowerCore__Database__ConnectionStrings__Sqlite + value: "Data Source=/data/library.db" + resources: + requests: + cpu: 25m + memory: 256Mi + limits: + cpu: 1000m + memory: 768Mi + startupProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 30 + readinessProbe: + httpGet: + path: /healthz + 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: data + mountPath: /data + - name: tmp + mountPath: /tmp + - name: logs + mountPath: /app/logs + volumes: + - name: data + persistentVolumeClaim: + claimName: library-data + - name: tmp + emptyDir: {} + - name: logs + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: library-web + namespace: fc-library + labels: + app.kubernetes.io/name: library-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.kubernetes.io/name: library-web + ports: + - name: http + port: 80 + targetPort: 8080 +--- +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/component: ingress + app.kubernetes.io/part-of: flowercore + app.kubernetes.io/managed-by: argocd + flowercore.io/tenant-id: system + flowercore.io/created-by: bluejay-infra +spec: + secretName: library-web-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - library.iamworkin.lan + duration: 720h # 30d (step-ca cap) + renewBefore: 240h # 10d +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: library-web + namespace: fc-library + labels: + app.kubernetes.io/name: library-web + app.kubernetes.io/component: ingress + 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(`library.iamworkin.lan`) + kind: Rule + services: + - name: library-web + port: 80 + tls: + secretName: library-web-tls