{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://home.keithjackson.dev/schemas/servicedefinition.schema.json", "title": "ServiceDefinition", "description": "Operator-facing onboarding contract for the home k3s + Longhorn platform. One ServiceDefinition per file under catalog/. Captures INTENT; the platform compiles it into concrete resources (see schemas/renderer-contract.md). Canonical source of truth: spec docs/13-service-catalog.md. Serves R-GITOPS-02; ties into R-NET-*, R-STATE-*, R-TLS-*, R-DNS-*, R-OBS-*, R-UPD-*, R-IDP-*. Schema version: 1.1.0 (adds shape invariant exposure:public => auth.mode!=none, docs/13 v1.1 / docs/15 / R-IDP-02).", "type": "object", "additionalProperties": false, "required": ["name", "source"], "properties": { "name": { "type": "string", "description": "DNS label -> .home.keithjackson.dev (docs/13, docs/09). Must be a valid RFC 1123 DNS label.", "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", "minLength": 1, "maxLength": 63 }, "kind": { "type": "string", "description": "PaaS container (default) or IaaS VM via KubeVirt (docs/13; R-PLAT-02/03).", "enum": ["container", "vm"], "default": "container" }, "source": { "type": "object", "description": "Workload source. container => image; vm => vmTemplate (CDI source). Exactly one, matching kind.", "additionalProperties": false, "minProperties": 1, "properties": { "image": { "type": "string", "description": "Container image reference (repo/image[:tag]). Required when kind=container.", "minLength": 1 }, "vmTemplate": { "type": "string", "description": "KubeVirt/CDI source image reference. Required when kind=vm.", "minLength": 1 } } }, "exposure": { "type": "string", "description": "internal = LAN-only (internal DNS + Gateway on LAN); public = additionally edge port-forwarded 80/443 (docs/09; R-DNS/R-NET).", "enum": ["internal", "public"], "default": "internal" }, "egress": { "type": "string", "description": "Egress class (docs/03; R-NET-05). internet = default WAN route; vpn = steered by Cilium Egress Gateway out the egress-gateway node's host PIA VPN tunnel, with a mandatory host kill-switch (ADR-0015; R-NET-06).", "enum": ["internet", "vpn"], "default": "internet" }, "storage": { "type": "array", "description": "Persistent volumes. If it must survive a rebuild it lives here, never on ephemeral/hostPath (docs/04; R-STATE-01/02/04).", "default": [], "items": { "type": "object", "additionalProperties": false, "required": ["name", "class"], "properties": { "name": { "type": "string", "description": "Volume identifier within the service (e.g. config, media, db).", "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" }, "size": { "type": "string", "description": "PVC request in Kubernetes quantity form (e.g. 5Gi). Required for the longhorn tier; may be omitted for the bulk tier (external mount, sized outside the cluster - docs/04).", "pattern": "^[1-9][0-9]*(Mi|Gi|Ti)$" }, "mountPath": { "type": "string", "description": "Optional explicit absolute container mount path for this volume. Default is /data/. Set it when the app expects a FIXED on-disk layout the default cannot express (e.g. Gitea keeps all state directly under /data). Renderer honors this verbatim; omitting it preserves the /data/ default for back-compat (R-STATE-01).", "pattern": "^/.*[^/]$" }, "class": { "type": "string", "description": "longhorn = 3-replica block storage for app/config/DB state (R-STATE-02); bulk = large external media tier, NOT 3x replicated (R-STATE-04).", "enum": ["longhorn", "bulk"], "default": "longhorn" } } } }, "dependencies": { "type": "array", "description": "Explicit east-west allow rules; everything else is denied (docs/03; R-NET-01/02). Empty => the service can reach only DNS.", "default": [], "items": { "type": "object", "additionalProperties": false, "required": ["to", "port"], "properties": { "to": { "type": "string", "description": "Destination service name (another catalog entry or a co-located component such as this service's DB).", "minLength": 1 }, "port": { "type": "integer", "description": "Destination TCP/UDP port.", "minimum": 1, "maximum": 65535 }, "protocol": { "type": "string", "enum": ["tcp", "udp"], "default": "tcp" } } } }, "ingressRoutes": { "type": "array", "description": "Hostnames/ports to expose north-south; rendered to HTTPRoute + Certificate + DNS (docs/08, docs/09; R-DNS/R-TLS).", "default": [], "items": { "type": "object", "additionalProperties": false, "required": ["host", "port"], "properties": { "host": { "type": "string", "description": "FQDN under the platform zone. Must be a