Adds apps/fc-distribution/{fc-distribution.yaml,kustomization.yaml,README.md}.
Ships the FlowerCore.Distribution service (Blazor + REST + MCP) backed by
Synology NFS for SQLite catalog + content-addressed blob root.
Contents:
- Namespace fc-distribution
- 3x OnePasswordItem (FlowerCore Code Signing CA informational + per-edition
signing keys for kiosk-standard and aistation-field)
- Deployment: localhost/fc-distribution:v202604232000 (already imported to
rke2-server via ctr), pinned to rke2-server nodeSelector because Synology
NFS ACL restricts writes to that node, emptyDir for /tmp + /app/logs,
inline NFS for /data (subPath distribution/data) and /blobs (subPath
distribution/blobs), Secret volume mounts for /signing/<edition>.
readOnlyRootFilesystem + runAsUser 1654 + drop ALL capabilities.
Probes: startup + readiness on /healthz, liveness on tcpSocket (defense
against future auth middleware accidentally gating /healthz).
- Service (ClusterIP :80 -> container :8080)
- Certificate (cert-manager ClusterIssuer step-ca-acme, dist.iamworkin.lan,
90d / 30d renew). pfSense Unbound override dist.iamworkin.lan ->
10.0.56.200 already in place (req'd for HTTP-01).
- IngressRoute (Traefik websecure, Host rule on dist.iamworkin.lan)
Env var keys align with the scaffold:
FlowerCore__Database__ConnectionStrings__Sqlite
FlowerCore__Distribution__Blobs__Root
FlowerCore__Distribution__Signing__EditionCerts__<slug>__{CertPath,KeyPath}
Consumer: ProvisioningAgent (USB-side, Phase 2) — see
FlowerCore.Notes/docs/infrastructure/usb-provisioning-architecture.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
3.7 KiB
Markdown
92 lines
3.7 KiB
Markdown
# fc-distribution — staged deployment (Phase 1, USB provisioning)
|
|
|
|
**Status:** manifests staged, **NOT YET APPLIED**. Image must be built +
|
|
imported and signing 1Password items confirmed before `git push`.
|
|
|
|
- Architecture: [`../../../FlowerCore.Notes/docs/infrastructure/usb-provisioning-architecture.md`](../../../FlowerCore.Notes/docs/infrastructure/usb-provisioning-architecture.md)
|
|
- Repo: `D:\git\FlowerCore\FlowerCore.Distribution\` (`README.md`, `CLAUDE.md`)
|
|
- Shared lib: `FlowerCore.Common` -> `FlowerCore.Shared.Distribution`
|
|
|
|
`FlowerCore.Distribution` publishes signed edition manifests (ECDSA P-256
|
|
over canonical JSON) and serves the SHA-256 content-addressed blob store
|
|
that USB builders pull from. The verifier embeds the `IAmWorkin ACME CA
|
|
Root CA` as the trust anchor; per-edition leaf signing material lives in
|
|
1Password and is mounted into the pod read-only.
|
|
|
|
## Deployment order (do NOT skip / reorder)
|
|
|
|
### 1. pfSense Unbound DNS — DONE 2026-04-23
|
|
|
|
`dist.iamworkin.lan -> 10.0.56.200` was added to pfSense Unbound out of band.
|
|
Verify before push:
|
|
|
|
```bash
|
|
nslookup dist.iamworkin.lan 10.0.56.1 # expect 10.0.56.200
|
|
python bluejay-infra/scripts/check-pfsense-dns.py
|
|
```
|
|
|
|
If this is missing, cert-manager HTTP-01 will silently back off ~2h. See
|
|
memory `feedback_pfsense_dns_required_for_acme.md`.
|
|
|
|
### 2. 1Password items required in vault `IAmWorkin`
|
|
|
|
| Item title | Item id | Used as |
|
|
|---|---|---|
|
|
| `FlowerCore Code Signing CA` | (existing) | Informational handle only — root CA is baked into the image at build time, not mounted |
|
|
| `FlowerCore Edition Signing Key - edition:kiosk-standard` | `3hf33egdvnni6jyuws3r737mqe` | Mounted at `/signing/kiosk-standard/` |
|
|
| `FlowerCore Edition Signing Key - edition:aistation-field` | `ccxrtsan5samfq4pfuczymacrq` | Mounted at `/signing/aistation-field/` |
|
|
|
|
Each edition item must publish three field labels (the operator turns
|
|
field labels into Secret keys verbatim):
|
|
|
|
- `certificate.pem` — leaf certificate
|
|
- `private-key.pem` — ECDSA P-256 private key
|
|
- `chain.pem` — leaf + intermediate (referenced by the env var as the
|
|
cert-path; the verifier uses this for signature path validation)
|
|
|
|
### 3. Build + import the image to rke2-server
|
|
|
|
The Pod is pinned to `rke2-server` because the Synology NFS export
|
|
`/volume1/kubernetes` only allows that node. Importing to the agents is
|
|
optional until the ACL is widened.
|
|
|
|
```bash
|
|
# From BLUEJAY-WS, in D:\git\FlowerCore\FlowerCore.Distribution
|
|
TAG="v$(date +%Y%m%d%H%M)"
|
|
dotnet.exe publish -c Release -o deploy/app \
|
|
src/FlowerCore.Distribution.Web/FlowerCore.Distribution.Web.csproj
|
|
podman build -t localhost/fc-distribution:$TAG -f deploy/Dockerfile.deploy deploy
|
|
podman save localhost/fc-distribution:$TAG -o /tmp/fc-distribution.tar
|
|
scp /tmp/fc-distribution.tar rke2-server:/tmp/
|
|
ssh rke2-server "sudo /var/lib/rancher/rke2/bin/ctr -a /run/k3s/containerd/containerd.sock -n k8s.io images import /tmp/fc-distribution.tar"
|
|
```
|
|
|
|
### 4. Bump the image tag + push
|
|
|
|
Edit `fc-distribution.yaml`, replace `localhost/fc-distribution:v202604231530`
|
|
with the tag from step 3, then:
|
|
|
|
```bash
|
|
cd D:/git/FlowerCore/bluejay-infra
|
|
python scripts/check-pfsense-dns.py
|
|
git add apps/fc-distribution/
|
|
git commit -m "feat(fc-distribution): deploy Phase 1 manifest publisher"
|
|
git push
|
|
```
|
|
|
|
ArgoCD picks up within ~3 minutes and creates `infra-fc-distribution`.
|
|
|
|
### 5. Verify
|
|
|
|
```bash
|
|
fcadmin_ssh noc1 '
|
|
kubectl -n argocd get application infra-fc-distribution
|
|
kubectl -n fc-distribution get certificate,pod,secret
|
|
curl -sk -m 8 -o /dev/null -w "HTTP %{http_code}\n" https://dist.iamworkin.lan/healthz
|
|
'
|
|
```
|
|
|
|
Expect: Certificate `Ready: True` within ~60s, `/healthz` HTTP 200, both
|
|
`edition-kiosk-standard` and `edition-aistation-field` Secrets present
|
|
with `certificate.pem`, `private-key.pem`, `chain.pem` keys.
|