@r8s/cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/catalog.js CHANGED
@@ -107,6 +107,13 @@ exports.components = [
107
107
  default: "'default'",
108
108
  description: 'Kubernetes namespace',
109
109
  },
110
+ {
111
+ name: 'instances',
112
+ type: 'number',
113
+ required: false,
114
+ default: '3',
115
+ description: 'CNPG cluster instances',
116
+ },
110
117
  {
111
118
  name: 'storage',
112
119
  type: 'string',
@@ -115,22 +122,46 @@ exports.components = [
115
122
  description: 'Storage size',
116
123
  },
117
124
  {
118
- name: 'operatorVersion',
125
+ name: 'storageClass',
119
126
  type: 'string',
120
127
  required: false,
121
- description: 'CNPG operator version',
128
+ description: 'Storage class for the data volume',
129
+ },
130
+ {
131
+ name: 'parameters',
132
+ type: 'Record<string, string>',
133
+ required: false,
134
+ description: 'PostgreSQL parameters (e.g. { max_connections: "200" })',
135
+ },
136
+ {
137
+ name: 'backup',
138
+ type: '{ destinationPath: string; endpointURL: string; credentialsSecret?: string; retention?: string; schedule?: string; compression?: string; encryption?: string }',
139
+ required: false,
140
+ description: 'Opt-in barman backup to S3 + ScheduledBackup. Credentials from the secrets backend or an existing Secret (keys access-key-id/secret-access-key) — never plaintext.',
141
+ },
142
+ {
143
+ name: 'rolloutRestartTargets',
144
+ type: '{ kind?: string; name: string; apiVersion?: string }[]',
145
+ required: false,
146
+ description: 'Workloads restarted when credentials rotate (rendered on the credentials static secret)',
147
+ },
148
+ {
149
+ name: 'postInitSQL',
150
+ type: 'string[]',
151
+ required: false,
152
+ description: 'SQL run once after bootstrap (roles, extensions)',
122
153
  },
123
154
  {
124
- name: 'password',
155
+ name: 'operatorVersion',
125
156
  type: 'string',
126
157
  required: false,
127
- description: 'Explicit password (else auto-generated)',
158
+ description: 'CNPG operator version',
128
159
  },
129
160
  {
130
161
  name: 'children',
131
162
  type: 'unknown',
132
163
  required: false,
133
- description: 'Child components (e.g. Keycloak CR)',
164
+ description: 'App/Database/Auth components (optional can set up cluster infra standalone)',
134
165
  },
135
166
  ],
136
167
  example: `import { Database } from '@r8s/recipes'\n\nexport default <Database name="api-db" storage="20Gi" />`,
@@ -168,6 +199,66 @@ exports.components = [
168
199
  ],
169
200
  example: `import { Endpoint } from '@r8s/recipes'\n\nexport default <Endpoint name="api" host="api.example.com" serviceName="api" />`,
170
201
  },
202
+ {
203
+ name: 'R8sCluster',
204
+ package: '@r8s/recipes',
205
+ category: 'Complete Solution',
206
+ description: 'Opinionated cluster foundation: cert-manager, external-dns (TSIG), Envoy Gateway, OpenBao VSO, Prometheus, Loki + FluentBit. All operators declared automatically.',
207
+ props: [
208
+ {
209
+ name: 'secrets',
210
+ type: '{ mount, path, authRef? }',
211
+ required: true,
212
+ description: 'OpenBao secrets backend config',
213
+ },
214
+ {
215
+ name: 'dns',
216
+ type: '{ server, zone, tsigPath, tsigKey? }',
217
+ required: true,
218
+ description: 'ExternalDNS with TSIG for RFC 2136',
219
+ },
220
+ {
221
+ name: 'gatewayClassName',
222
+ type: 'string',
223
+ required: false,
224
+ default: "'eg'",
225
+ description: 'Envoy Gateway class name',
226
+ },
227
+ {
228
+ name: 'labels',
229
+ type: 'Record<string, string>',
230
+ required: false,
231
+ description: 'Default labels',
232
+ },
233
+ {
234
+ name: 'operators',
235
+ type: 'Operator[]',
236
+ required: false,
237
+ description: 'Pre-installed operators (skip auto-declare)',
238
+ },
239
+ {
240
+ name: 'logsNamespace',
241
+ type: 'string',
242
+ required: false,
243
+ default: "'logging'",
244
+ description: 'Namespace for LokiStack and logging resources',
245
+ },
246
+ {
247
+ name: 'logsStorageClass',
248
+ type: 'string',
249
+ required: false,
250
+ default: "'standard'",
251
+ description: 'Storage class for Loki logs',
252
+ },
253
+ {
254
+ name: 'children',
255
+ type: 'unknown',
256
+ required: true,
257
+ description: 'App/Database/Auth components',
258
+ },
259
+ ],
260
+ example: `import { R8sCluster, Platform, App, Database } from '@r8s/recipes'\n\nexport default (\n <R8sCluster\n secrets={{ mount: 'secret', path: 'production' }}\n dns={{ server: 'ns1.example.com', zone: 'example.com', tsigPath: 'dns/tsig' }}\n >\n <Platform namespace="production">\n <Database name="api-db" storage="20Gi" />\n <App name="api" image="api:v1" host="api.example.com" />\n </Platform>\n </R8sCluster>\n)`,
261
+ },
171
262
  {
172
263
  name: 'Platform',
173
264
  package: '@r8s/recipes',
@@ -409,7 +500,12 @@ exports.components = [
409
500
  required: true,
410
501
  description: 'External or managed Redis',
411
502
  },
412
- { name: 'adminSecret', type: 'string', required: true, description: 'Admin secret name' },
503
+ {
504
+ name: 'admin',
505
+ type: '{ password? } | { existingSecret? }',
506
+ required: true,
507
+ description: 'Admin credentials (creates Secret by default)',
508
+ },
413
509
  {
414
510
  name: 'name',
415
511
  type: 'string',
@@ -431,7 +527,7 @@ exports.components = [
431
527
  description: 'TLS config',
432
528
  },
433
529
  ],
434
- example: `import { Superset } from '@r8s/superset'\n\nexport default <Superset host="analytics.example.com" database={{ host: 'db-rw', database: 'superset', user: 'superset', passwordSecret: 'superset-db' }} redis={{ create: true }} adminSecret="superset-admin" />`,
530
+ example: `import { Superset } from '@r8s/superset'\n\nexport default <Superset host="analytics.example.com" database={{ host: 'db-rw', database: 'superset', user: 'superset', passwordSecret: 'superset-db', passwordKey: 'password' }} redis={{ create: true }} admin={{ existingSecret: 'superset-admin-credentials' }} />`,
435
531
  },
436
532
  {
437
533
  name: 'RustFS',
@@ -508,6 +604,985 @@ exports.components = [
508
604
  ],
509
605
  example: `import { WireGuard } from '@r8s/wireguard'\n\nexport default <WireGuard host="vpn.example.com" peers={3} />`,
510
606
  },
607
+ {
608
+ name: 'N8n',
609
+ package: '@r8s/n8n',
610
+ category: 'Apps & Automation',
611
+ description: 'n8n workflow automation — editor, Postgres persistence, Redis queue mode, webhook endpoints',
612
+ props: [
613
+ {
614
+ name: 'name',
615
+ type: 'string',
616
+ required: false,
617
+ description: "Resource name (defaults to 'n8n')",
618
+ },
619
+ {
620
+ name: 'namespace',
621
+ type: 'string',
622
+ required: false,
623
+ description: 'Kubernetes namespace (inherited from Platform context when omitted)',
624
+ },
625
+ {
626
+ name: 'version',
627
+ type: 'string',
628
+ required: false,
629
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
630
+ },
631
+ {
632
+ name: 'host',
633
+ type: 'string',
634
+ required: true,
635
+ description: 'Public hostname for the editor and webhooks (required)',
636
+ },
637
+ {
638
+ name: 'replicas',
639
+ type: 'number',
640
+ required: false,
641
+ description: 'Number of editor replicas when not running in queue mode (defaults to 1)',
642
+ },
643
+ {
644
+ name: 'queueMode',
645
+ type: 'boolean',
646
+ required: false,
647
+ description: 'Redis-backed queue mode. Adds a Redis master/replica set and a worker Deployment so webhook ingestion and heavy executions scale independently.',
648
+ },
649
+ {
650
+ name: 'workers',
651
+ type: 'number',
652
+ required: false,
653
+ description: 'Queue worker replicas (defaults to 2, only used with queueMode)',
654
+ },
655
+ {
656
+ name: 'storage',
657
+ type: 'string',
658
+ required: false,
659
+ description: "Storage request for the CNPG Postgres cluster (defaults to '10Gi')",
660
+ },
661
+ {
662
+ name: 'encryptionKeySecretName',
663
+ type: 'string',
664
+ required: false,
665
+ description: 'Name of an existing Secret containing key `encryptionKey`. n8n encrypts all workflow credentials with this key — lose it and every stored credential is unreadable. Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions the key automatically. Plaintext keys are not supported.',
666
+ },
667
+ {
668
+ name: 'resources',
669
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
670
+ required: false,
671
+ description: 'Requested editor resources',
672
+ },
673
+ {
674
+ name: 'tls',
675
+ type: '{ secretName: string clusterIssuer: string }',
676
+ required: false,
677
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
678
+ },
679
+ ],
680
+ example: "import { Platform } from '@r8s/recipes'\nimport { N8n } from '@r8s/n8n'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <N8n name=\"n8n\" host=\"n8n.example.com\" queueMode workers={3} />\n </Platform>\n)",
681
+ },
682
+ {
683
+ name: 'Nextcloud',
684
+ package: '@r8s/nextcloud',
685
+ category: 'Collaboration & Productivity',
686
+ description: 'Nextcloud file cloud — Postgres file index, Redis cache, S3-compatible primary storage, cron background jobs',
687
+ props: [
688
+ {
689
+ name: 'name',
690
+ type: 'string',
691
+ required: false,
692
+ description: "Resource name (defaults to 'nextcloud')",
693
+ },
694
+ {
695
+ name: 'namespace',
696
+ type: 'string',
697
+ required: false,
698
+ description: "Kubernetes namespace (defaults to 'default')",
699
+ },
700
+ {
701
+ name: 'version',
702
+ type: 'string',
703
+ required: false,
704
+ description: "Container image tag (defaults to '31-apache' — pin a version in production)",
705
+ },
706
+ {
707
+ name: 'host',
708
+ type: 'string',
709
+ required: true,
710
+ description: 'Public hostname for the web UI and WebDAV (required)',
711
+ },
712
+ {
713
+ name: 'replicas',
714
+ type: 'number',
715
+ required: false,
716
+ description: 'Number of replicas. Safe to scale beyond 1 when `objectStorage` is configured (file blobs live in S3) and `cache` is enabled — Nextcloud becomes effectively stateless. Requires a StorageClass with ReadWriteMany support for the /var/www/html claim.',
717
+ },
718
+ {
719
+ name: 'cache',
720
+ type: 'boolean',
721
+ required: false,
722
+ description: 'Provision a Redis replication set for file locking and caching (default: true)',
723
+ },
724
+ {
725
+ name: 'storage',
726
+ type: 'string',
727
+ required: false,
728
+ description: "Size of the PersistentVolumeClaim backing /var/www/html (defaults to '10Gi'). Apps, config and the data directory all live in this tree.",
729
+ },
730
+ {
731
+ name: 'storageClassName',
732
+ type: 'string',
733
+ required: false,
734
+ description: 'StorageClass for the /var/www/html PersistentVolumeClaim. Must provide ReadWriteMany when `replicas` > 1 (e.g. NFS or EFS). Defaults to the cluster default StorageClass when omitted.',
735
+ },
736
+ {
737
+ name: 'objectStorage',
738
+ type: "{ /** S3 host WITHOUT protocol/scheme, e.g. s3.internal.example.com */ endpoint: string /** Bucket used for user files */ bucket: string /** Name of the Secret holding accessKey / secretKey */ credentialsSecret: string /** Region string for the S3 client (defaults to 'us-east-1') */ region?: string /** TCP port of the S3 endpoint (defaults to the provider default, typically 443) */ port?: number /** Use TLS against the S3 endpoint (default: true) */ ssl?: boolean }",
739
+ required: false,
740
+ description: 'S3-compatible object storage used as primary storage for files (RustFS in the platform). Reference a bucket whose credentials live in a Secret provisioned by the secrets backend (keys: accessKey, secretKey) — never plaintext.',
741
+ },
742
+ {
743
+ name: 'secretsName',
744
+ type: 'string',
745
+ required: false,
746
+ description: 'Name of an existing Secret holding the Nextcloud app secrets (key: adminPassword). Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions them automatically. Plaintext admin passwords are not supported.',
747
+ },
748
+ {
749
+ name: 'resources',
750
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
751
+ required: false,
752
+ description: 'Requested resources',
753
+ },
754
+ {
755
+ name: 'tls',
756
+ type: '{ secretName: string clusterIssuer: string }',
757
+ required: false,
758
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
759
+ },
760
+ ],
761
+ example: "import { Platform } from '@r8s/recipes'\nimport { Nextcloud } from '@r8s/nextcloud'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <Nextcloud\n name=\"cloud\"\n host=\"cloud.example.com\"\n objectStorage={{\n endpoint: 's3.internal.example.com',\n bucket: 'cloud-files',\n credentialsSecret: 'cloud-files-credentials',\n }}\n />\n </Platform>\n)",
762
+ },
763
+ {
764
+ name: 'Outline',
765
+ package: '@r8s/outline',
766
+ category: 'Collaboration & Productivity',
767
+ description: 'Outline wiki — Postgres persistence, Redis queue, S3 attachments, OIDC SSO via Keycloak',
768
+ props: [
769
+ {
770
+ name: 'name',
771
+ type: 'string',
772
+ required: false,
773
+ description: "Resource name (defaults to 'outline')",
774
+ },
775
+ {
776
+ name: 'namespace',
777
+ type: 'string',
778
+ required: false,
779
+ description: "Kubernetes namespace (defaults to 'default')",
780
+ },
781
+ {
782
+ name: 'version',
783
+ type: 'string',
784
+ required: false,
785
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
786
+ },
787
+ { name: 'host', type: 'string', required: true, description: 'Public hostname for the wiki' },
788
+ {
789
+ name: 'storage',
790
+ type: 'string',
791
+ required: false,
792
+ description: "Storage request for the CNPG Postgres cluster (defaults to '10Gi')",
793
+ },
794
+ {
795
+ name: 'replicas',
796
+ type: 'number',
797
+ required: false,
798
+ description: 'Number of replicas (Outline is stateless — scale freely)',
799
+ },
800
+ {
801
+ name: 'cache',
802
+ type: 'boolean',
803
+ required: false,
804
+ description: 'Provision a Redis cluster for the queue and rate limiting (default: true)',
805
+ },
806
+ {
807
+ name: 'objectStorage',
808
+ type: "{ /** S3 endpoint URL, e.g. https://s3.internal.example.com */ endpoint: string /** Bucket name for attachments */ bucket: string /** Name of the Secret holding accessKey / secretKey */ credentialsSecret: string /** Region string for Outline's S3 client (defaults to 'us-east-1') */ region?: string }",
809
+ required: false,
810
+ description: 'S3-compatible object storage for attachments (RustFS in the platform). Reference a bucket whose credentials live in a Secret provisioned by the secrets backend (keys: accessKey, secretKey) — never plaintext.',
811
+ },
812
+ {
813
+ name: 'sso',
814
+ type: '{ issuer: string clientId: string clientSecretRef: SecretRef scopes?: string }',
815
+ required: false,
816
+ description: 'OIDC SSO client — register Outline as a client in Keycloak (the Auth recipe) and reference the client secret through the backend.',
817
+ },
818
+ {
819
+ name: 'secretsName',
820
+ type: 'string',
821
+ required: false,
822
+ description: 'Name of an existing Secret holding `secretKey` and `utilsSecret`. Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions them.',
823
+ },
824
+ {
825
+ name: 'resources',
826
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
827
+ required: false,
828
+ description: 'Requested resources',
829
+ },
830
+ {
831
+ name: 'tls',
832
+ type: '{ secretName: string clusterIssuer: string }',
833
+ required: false,
834
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
835
+ },
836
+ ],
837
+ example: "import { Platform } from '@r8s/recipes'\nimport { Outline } from '@r8s/outline'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <Outline\n name=\"wiki\"\n host=\"wiki.example.com\"\n objectStorage={{\n endpoint: 'https://s3.internal.example.com',\n bucket: 'wiki-attachments',\n credentialsSecret: 'wiki-attachments-credentials',\n }}\n sso={{\n issuer: 'https://keycloak.example.com/realms/platform',\n clientId: 'outline',\n clientSecretRef: { secret: 'outline-sso', key: 'clientSecret' },\n }}\n />\n </Platform>\n)",
838
+ },
839
+ {
840
+ name: 'ChromaDb',
841
+ package: '@r8s/chromadb',
842
+ category: 'AI & Assistants',
843
+ description: 'ChromaDB vector database — persistent storage, optional Postgres metadata backend, token auth, CPU autoscaling',
844
+ props: [
845
+ {
846
+ name: 'name',
847
+ type: 'string',
848
+ required: false,
849
+ description: "Resource name (defaults to 'chromadb')",
850
+ },
851
+ {
852
+ name: 'namespace',
853
+ type: 'string',
854
+ required: false,
855
+ description: "Kubernetes namespace (defaults to 'default')",
856
+ },
857
+ {
858
+ name: 'version',
859
+ type: 'string',
860
+ required: false,
861
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
862
+ },
863
+ {
864
+ name: 'host',
865
+ type: 'string',
866
+ required: true,
867
+ description: 'Public hostname for the vector API (required)',
868
+ },
869
+ {
870
+ name: 'port',
871
+ type: 'number',
872
+ required: false,
873
+ description: 'Port Chroma listens on (defaults to 8000)',
874
+ },
875
+ {
876
+ name: 'replicas',
877
+ type: 'number',
878
+ required: false,
879
+ description: 'Number of replicas (defaults to 1). The embedded data PVC is ReadWriteOnce — extra replicas on other nodes cause Multi-Attach errors; scale out only with a ReadWriteMany StorageClass via `storageClassName` (required for `autoscaling`).',
880
+ },
881
+ {
882
+ name: 'storage',
883
+ type: 'string',
884
+ required: false,
885
+ description: "Persistent storage size for the embedded data volume (defaults to '50Gi')",
886
+ },
887
+ {
888
+ name: 'storageClassName',
889
+ type: 'string',
890
+ required: false,
891
+ description: 'StorageClass for the data PersistentVolumeClaim (optional — cluster default)',
892
+ },
893
+ {
894
+ name: 'probePath',
895
+ type: 'string',
896
+ required: false,
897
+ description: "HTTP path for liveness/readiness probes (defaults to '/api/v2/heartbeat' — the current image API). Set '/api/v1/heartbeat' for older images still serving the v1 API.",
898
+ },
899
+ {
900
+ name: 'auth',
901
+ type: 'boolean',
902
+ required: false,
903
+ description: 'Require token authentication for the server (defaults to false)',
904
+ },
905
+ {
906
+ name: 'authTokenSecretName',
907
+ type: 'string',
908
+ required: false,
909
+ description: 'Name of an existing Secret holding key `token` — the Chroma server auth credential. Required when `auth` is true, unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions the token at path `<path>/<name>/auth-token` (key: token). Plaintext credentials are not supported.',
910
+ },
911
+ {
912
+ name: 'autoscaling',
913
+ type: 'boolean',
914
+ required: false,
915
+ description: 'Autoscale the Deployment via a CPU-based HorizontalPodAutoscaler (defaults to false)',
916
+ },
917
+ {
918
+ name: 'pg',
919
+ type: 'boolean',
920
+ required: false,
921
+ description: 'Provision a CNPG Postgres cluster as the metadata store (defaults to false)',
922
+ },
923
+ {
924
+ name: 'resources',
925
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
926
+ required: false,
927
+ description: 'Requested resources',
928
+ },
929
+ {
930
+ name: 'tls',
931
+ type: '{ secretName: string clusterIssuer: string }',
932
+ required: false,
933
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
934
+ },
935
+ ],
936
+ example: 'import { ChromaDb } from \'@r8s/chromadb\'\n\nexport default <ChromaDb name="vectors" host="vectors.example.com" />',
937
+ },
938
+ {
939
+ name: 'Supabase',
940
+ package: '@r8s/supabase',
941
+ category: 'Data & Analytics',
942
+ description: 'Supabase backend platform — Postgres core with GoTrue auth, PostgREST, Realtime, Storage API (S3/RustFS) and ImgProxy. This is Supabase, NOT Apache Superset.',
943
+ props: [
944
+ {
945
+ name: 'name',
946
+ type: 'string',
947
+ required: false,
948
+ description: "Resource name — base for every derived resource (defaults to 'supabase')",
949
+ },
950
+ {
951
+ name: 'namespace',
952
+ type: 'string',
953
+ required: false,
954
+ description: "Kubernetes namespace (defaults to 'default')",
955
+ },
956
+ {
957
+ name: 'host',
958
+ type: 'string',
959
+ required: true,
960
+ description: 'Public hostname for the REST API root — PostgREST (required)',
961
+ },
962
+ {
963
+ name: 'replicas',
964
+ type: 'number',
965
+ required: false,
966
+ description: 'Replicas per service (defaults to 1; PostgREST/GoTrue scale horizontally)',
967
+ },
968
+ {
969
+ name: 'storage',
970
+ type: 'string',
971
+ required: false,
972
+ description: "Postgres cluster storage size for the Database core (defaults to '10Gi')",
973
+ },
974
+ {
975
+ name: 'storageApi',
976
+ type: 'boolean',
977
+ required: false,
978
+ description: 'Render the Storage API service (defaults to true). Set false to run a minimal auth + REST-only Supabase. The S3 objectStorage prop is always required so a bucket is declared for the platform.',
979
+ },
980
+ {
981
+ name: 'objectStorage',
982
+ type: '{ /** S3 endpoint URL, e.g. https://s3.internal.example.com */ endpoint: string /** Bucket for uploads and stored files */ bucket: string /** Name of the Secret holding accessKey / secretKey */ credentialsSecret: string }',
983
+ required: true,
984
+ description: 'S3-compatible object storage for the Storage API (RustFS in the platform). Reference a bucket whose credentials live in a Secret provisioned by the secrets backend (keys: accessKey, secretKey) — never plaintext.',
985
+ },
986
+ {
987
+ name: 'region',
988
+ type: 'string',
989
+ required: false,
990
+ description: "S3 region reported to the Storage API (GLOBAL_S3_REGION, defaults to 'us-east-1'). For S3-compatible stores like RustFS any consistent region works — keep it aligned with the provider's default.",
991
+ },
992
+ {
993
+ name: 'jwtSecretsName',
994
+ type: 'string',
995
+ required: false,
996
+ description: 'Name of an existing Secret holding the Supabase JWT bundle with keys `jwtSecret`, `anonKey`, `serviceRoleKey` and `referrerURLs`. Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions the bundle at path `<path>/<name>/jwt`. Plaintext JWT secrets are not supported.',
997
+ },
998
+ {
999
+ name: 'uriAllowList',
1000
+ type: 'string | string[]',
1001
+ required: false,
1002
+ description: "Additional redirect URLs GoTrue may send users to after signup, magic-link or OAuth flows (GOTRUE_URI_ALLOW_LIST). The site URL is always allowed; pass a list (joined with ',') or a pre-joined string.",
1003
+ },
1004
+ {
1005
+ name: 'resources',
1006
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
1007
+ required: false,
1008
+ description: 'Requested resources — applied to every service in the suite',
1009
+ },
1010
+ {
1011
+ name: 'tls',
1012
+ type: '{ secretName: string clusterIssuer: string }',
1013
+ required: false,
1014
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
1015
+ },
1016
+ ],
1017
+ example: "import { Platform } from '@r8s/recipes'\nimport { Supabase } from '@r8s/supabase'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <Supabase\n name=\"backend\"\n host=\"backend.example.com\"\n objectStorage={{\n endpoint: 'https://s3.internal.example.com',\n bucket: 'backend-uploads',\n credentialsSecret: 'backend-object-store-credentials',\n }}\n />\n </Platform>\n)",
1018
+ },
1019
+ {
1020
+ name: 'Odoo',
1021
+ package: '@r8s/odoo',
1022
+ category: 'Apps & Automation',
1023
+ description: 'Odoo ERP — Postgres persistence, filestore PVC, worker tuning, master password via secrets backend',
1024
+ props: [
1025
+ {
1026
+ name: 'name',
1027
+ type: 'string',
1028
+ required: false,
1029
+ description: "Resource name (defaults to 'odoo')",
1030
+ },
1031
+ {
1032
+ name: 'namespace',
1033
+ type: 'string',
1034
+ required: false,
1035
+ description: "Kubernetes namespace (defaults to 'default')",
1036
+ },
1037
+ {
1038
+ name: 'version',
1039
+ type: 'string',
1040
+ required: false,
1041
+ description: "Container image tag (defaults to '18' — pin a version in production)",
1042
+ },
1043
+ {
1044
+ name: 'host',
1045
+ type: 'string',
1046
+ required: true,
1047
+ description: 'Public hostname for the ERP web UI (required)',
1048
+ },
1049
+ {
1050
+ name: 'replicas',
1051
+ type: 'number',
1052
+ required: false,
1053
+ description: 'Number of replicas. Must stay at 1 — the filestore PVC is ReadWriteOnce and cannot attach to multiple pods (defaults to 1).',
1054
+ },
1055
+ {
1056
+ name: 'filestore',
1057
+ type: 'string',
1058
+ required: false,
1059
+ description: "Size of the filestore PersistentVolumeClaim (defaults to '20Gi'). Odoo stores attachments and binary fields here.",
1060
+ },
1061
+ {
1062
+ name: 'workers',
1063
+ type: 'number',
1064
+ required: false,
1065
+ description: 'Odoo process-level worker processes, rendered into odoo.conf (defaults to 2). Rule of thumb: (CPU threads * 2) + 1, accounting for cron workers.',
1066
+ },
1067
+ {
1068
+ name: 'masterPasswordSecretName',
1069
+ type: 'string',
1070
+ required: false,
1071
+ description: 'Name of an existing Secret containing key `masterPassword`. Odoo requires this to manage the super-admin (`/web/database/manager`). Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions the password automatically. Plaintext passwords are not supported.',
1072
+ },
1073
+ {
1074
+ name: 'resources',
1075
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
1076
+ required: false,
1077
+ description: 'Requested resources',
1078
+ },
1079
+ {
1080
+ name: 'tls',
1081
+ type: '{ secretName: string clusterIssuer: string }',
1082
+ required: false,
1083
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
1084
+ },
1085
+ ],
1086
+ example: "import { Platform } from '@r8s/recipes'\nimport { Odoo } from '@r8s/odoo'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <Odoo name=\"erp\" host=\"erp.example.com\" workers={4} />\n </Platform>\n)",
1087
+ },
1088
+ {
1089
+ name: 'OpenWebui',
1090
+ package: '@r8s/open-webui',
1091
+ category: 'AI & Assistants',
1092
+ description: 'Open WebUI — chat frontend for OpenAI-compatible backends, Postgres persistence, uploads/RAG storage, OIDC SSO, optional Redis cache',
1093
+ props: [
1094
+ {
1095
+ name: 'name',
1096
+ type: 'string',
1097
+ required: false,
1098
+ description: "Resource name (defaults to 'open-webui')",
1099
+ },
1100
+ {
1101
+ name: 'namespace',
1102
+ type: 'string',
1103
+ required: false,
1104
+ description: "Kubernetes namespace (defaults to 'default')",
1105
+ },
1106
+ {
1107
+ name: 'version',
1108
+ type: 'string',
1109
+ required: false,
1110
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
1111
+ },
1112
+ {
1113
+ name: 'host',
1114
+ type: 'string',
1115
+ required: true,
1116
+ description: 'Public hostname for the chat UI (required)',
1117
+ },
1118
+ {
1119
+ name: 'replicas',
1120
+ type: 'number',
1121
+ required: false,
1122
+ description: 'Number of replicas (defaults to 1). Multiple replicas need a shared object store for uploads/RAG (this recipe errors when `storage` is set with replicas > 1 — its PVC is ReadWriteOnce) and Redis-backed websocket coordination (WEBSOCKET_MANAGER=redis + REDIS_URL).',
1123
+ },
1124
+ {
1125
+ name: 'storage',
1126
+ type: 'string',
1127
+ required: false,
1128
+ description: "PVC size for uploads and RAG document storage (e.g. '10Gi'). When set, a `${name}-uploads` PersistentVolumeClaim is rendered and mounted at /app/backend/data. WebService cannot express volume mounts, which is why this component composes a raw Deployment (probes /health:8080). The PVC is ReadWriteOnce — combining this with replicas > 1 throws (single-node attach); multi-replica installs must move files/RAG to an S3-compatible store and set WEBSOCKET_MANAGER=redis.",
1129
+ },
1130
+ {
1131
+ name: 'backend',
1132
+ type: 'string',
1133
+ required: false,
1134
+ description: "OpenAI-compatible API base URL for the model backend (defaults to 'https://api.berget.ai/v1'). The key itself is never passed as a prop — it arrives via secretKeyRef from the secrets bundle below.",
1135
+ },
1136
+ {
1137
+ name: 'secretsName',
1138
+ type: 'string',
1139
+ required: false,
1140
+ description: 'Name of an existing Secret holding `modelApiKey` (the key used to call `backend`) and `secretKey` (the WEBUI_SECRET_KEY used to sign auth tokens). Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions both keys automatically. Plaintext keys are not supported.',
1141
+ },
1142
+ {
1143
+ name: 'sso',
1144
+ type: "{ /** OIDC discovery issuer, e.g. https://keycloak.example.com/realms/platform */ issuer: string /** Client id registered at the issuer (non-sensitive) */ clientId: string /** Reference to the Kubernetes Secret holding the client secret */ clientSecretRef: SecretRef /** Scope list (defaults to 'openid email profile') */ scopes?: string }",
1145
+ required: false,
1146
+ description: 'OAuth/OIDC SSO client — register Open WebUI as a client in Keycloak (the Auth recipe) and reference the client secret through the backend. Uses the upstream OAUTH_* env names; OPENID_PROVIDER_URL carries the issuer (Open WebUI appends /.well-known/openid-configuration itself).',
1147
+ },
1148
+ {
1149
+ name: 'cache',
1150
+ type: 'boolean',
1151
+ required: false,
1152
+ description: 'Provision a redis-backed replication group for caching/events (default: false)',
1153
+ },
1154
+ {
1155
+ name: 'offline',
1156
+ type: 'boolean',
1157
+ required: false,
1158
+ description: 'Air-gapped installs: sets OFFLINE_MODE (disable runtime model/param fetches), removes the update checks and disables the native Ollama API — only OpenAI-compatible backends are served (default: false).',
1159
+ },
1160
+ {
1161
+ name: 'resources',
1162
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
1163
+ required: false,
1164
+ description: 'Requested resources',
1165
+ },
1166
+ {
1167
+ name: 'tls',
1168
+ type: '{ secretName: string clusterIssuer: string }',
1169
+ required: false,
1170
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
1171
+ },
1172
+ ],
1173
+ example: "import { Platform } from '@r8s/recipes'\nimport { OpenWebui } from '@r8s/open-webui'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <OpenWebui\n name=\"chat\"\n host=\"chat.example.com\"\n version=\"v0.6.5\"\n storage=\"10Gi\"\n />\n </Platform>\n)",
1174
+ },
1175
+ {
1176
+ name: 'LibreChat',
1177
+ package: '@r8s/librechat',
1178
+ category: 'AI & Assistants',
1179
+ description: 'LibreChat multi-model AI chat — MongoDB (provisioned externally), Redis sessions, optional Meilisearch, OIDC SSO',
1180
+ props: [
1181
+ {
1182
+ name: 'name',
1183
+ type: 'string',
1184
+ required: false,
1185
+ description: "Resource name (defaults to 'librechat')",
1186
+ },
1187
+ {
1188
+ name: 'namespace',
1189
+ type: 'string',
1190
+ required: false,
1191
+ description: "Kubernetes namespace (defaults to 'default')",
1192
+ },
1193
+ {
1194
+ name: 'version',
1195
+ type: 'string',
1196
+ required: false,
1197
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
1198
+ },
1199
+ {
1200
+ name: 'host',
1201
+ type: 'string',
1202
+ required: true,
1203
+ description: 'Public hostname for the chat UI (required)',
1204
+ },
1205
+ {
1206
+ name: 'port',
1207
+ type: 'number',
1208
+ required: false,
1209
+ description: 'Port the app listens on in-container (defaults to 3080)',
1210
+ },
1211
+ {
1212
+ name: 'replicas',
1213
+ type: 'number',
1214
+ required: false,
1215
+ description: 'Number of replicas (defaults to 1)',
1216
+ },
1217
+ {
1218
+ name: 'mongodb',
1219
+ type: 'MongoConnection',
1220
+ required: true,
1221
+ description: 'External MongoDB connection (REQUIRED). LibreChat stores users, conversations and messages in MongoDB — this component does NOT provision it. Run MongoDB separately (replica-set StatefulSet, operator or managed service) and point this prop at it.',
1222
+ },
1223
+ {
1224
+ name: 'cache',
1225
+ type: 'boolean',
1226
+ required: false,
1227
+ description: 'Provision a redis replication group for session caching (default: true)',
1228
+ },
1229
+ {
1230
+ name: 'search',
1231
+ type: 'boolean',
1232
+ required: false,
1233
+ description: 'Add a Meilisearch sidecar service for full-text / RAG search (default: false). MEILI_MASTER_KEY is shared from the app secrets bundle (key: meiliMasterKey). Sets SEARCH=true on the app so it actually queries the meilisearch instance.',
1234
+ },
1235
+ {
1236
+ name: 'sso',
1237
+ type: '{ issuer: string clientId: string clientSecretRef: SecretRef scopes?: string }',
1238
+ required: false,
1239
+ description: 'OIDC SSO client — register LibreChat as a client in Keycloak (the Auth recipe) and reference the client secret through the backend. Uses the upstream OPENID_* env names; ALLOW_SOCIAL_LOGIN plus DOMAIN_SERVER/DOMAIN_CLIENT are set from `host`.',
1240
+ },
1241
+ {
1242
+ name: 'backend',
1243
+ type: 'string',
1244
+ required: false,
1245
+ description: 'OpenAI-compatible API base URL for model calls (defaults to https://api.berget.ai/v1)',
1246
+ },
1247
+ {
1248
+ name: 'secretsName',
1249
+ type: 'string',
1250
+ required: false,
1251
+ description: 'Name of an existing Secret holding `secretKey`, `modelApiKey`, the multi-user session credentials `jwtSecret`, `jwtRefreshSecret`, `credsKey`, `credsIv` — and `meiliMasterKey` when `search` is enabled. Hex sizing: jwtSecret / jwtRefreshSecret / credsKey are 64 hex chars (32 bytes); credsIv is 32 hex chars (16 bytes — AES-IV). Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions them. Plaintext secrets are not supported.',
1252
+ },
1253
+ {
1254
+ name: 'resources',
1255
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
1256
+ required: false,
1257
+ description: 'Requested resources',
1258
+ },
1259
+ {
1260
+ name: 'tls',
1261
+ type: '{ secretName: string clusterIssuer: string }',
1262
+ required: false,
1263
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
1264
+ },
1265
+ ],
1266
+ example: "import { Platform } from '@r8s/recipes'\nimport { LibreChat } from '@r8s/librechat'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <LibreChat\n name=\"chat\"\n host=\"chat.example.com\"\n mongodb={{ host: 'mongo.data.svc.cluster.local', passwordSecret: 'chat-mongodb-credentials' }}\n sso={{\n issuer: 'https://keycloak.example.com/realms/platform',\n clientId: 'librechat',\n clientSecretRef: { secret: 'librechat-sso', key: 'clientSecret' },\n }}\n />\n </Platform>\n)",
1267
+ },
1268
+ {
1269
+ name: 'EuroOffice',
1270
+ package: '@r8s/eurooffice',
1271
+ category: 'Collaboration & Productivity',
1272
+ description: 'EuroOffice collaborative document suite — Postgres persistence, S3 blob storage, LibreOffice conversions, SMTP delivery and websocket collaboration',
1273
+ props: [
1274
+ {
1275
+ name: 'name',
1276
+ type: 'string',
1277
+ required: false,
1278
+ description: "Resource name (defaults to 'eurooffice')",
1279
+ },
1280
+ {
1281
+ name: 'namespace',
1282
+ type: 'string',
1283
+ required: false,
1284
+ description: "Kubernetes namespace (defaults to 'default')",
1285
+ },
1286
+ {
1287
+ name: 'version',
1288
+ type: 'string',
1289
+ required: false,
1290
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
1291
+ },
1292
+ {
1293
+ name: 'host',
1294
+ type: 'string',
1295
+ required: true,
1296
+ description: 'Public hostname for the document suite (required)',
1297
+ },
1298
+ {
1299
+ name: 'replicas',
1300
+ type: 'number',
1301
+ required: false,
1302
+ description: 'Number of app replicas. With `websockets` enabled (the default) this defaults to **1**: websocket sessions are pinned to the pod that accepted the connection and the workload has no session affinity, so >1 replicas means collaborators on different pods stop seeing each other live. An explicitly set `replicas` is rendered as asked — pair it with a sticky-session (source-IP) ingress strategy for live co-editing. With `websockets: false` the default is 2 (the app is stateless — scale freely).',
1303
+ },
1304
+ {
1305
+ name: 'websockets',
1306
+ type: 'boolean',
1307
+ required: false,
1308
+ description: 'Collaborative editing over websockets (default: true). Disable only for single-user or read-only deployments — document presence, cursor sharing and live co-editing all rely on websockets.',
1309
+ },
1310
+ {
1311
+ name: 'objectStorage',
1312
+ type: "{ /** S3 endpoint URL, e.g. https://s3.internal.example.com */ endpoint: string /** Bucket name for document blobs */ bucket: string /** Name of the Secret holding accessKey / secretKey */ credentialsSecret: string /** Region string for the S3 client (defaults to 'us-east-1') */ region?: string }",
1313
+ required: true,
1314
+ description: 'S3-compatible object storage for document blobs and attachments (RustFS in the platform). Required — reference a bucket whose credentials live in a Secret provisioned by the secrets backend (keys: accessKey, secretKey) — never plaintext.',
1315
+ },
1316
+ {
1317
+ name: 'smtp',
1318
+ type: '{ /** SMTP server hostname, e.g. smtp.example.com */ host: string /** SMTP port (defaults to 587) */ port?: number /** From address for outgoing mail, e.g. no-reply@example.com */ from?: string }',
1319
+ required: false,
1320
+ description: 'Outgoing SMTP for invitations and notifications. The SMTP password is delivered via secretKeyRef from the `${name}-secrets` bundle (key: smtpPassword) — never plaintext.',
1321
+ },
1322
+ {
1323
+ name: 'conversions',
1324
+ type: 'boolean',
1325
+ required: false,
1326
+ description: 'LibreOffice headless document-conversion workers (same app image, which must include soffice). Adds a `${name}-soffice` Deployment + Service; the app reaches it at SOFFICE_HOST:SOFFICE_PORT. Workers serve the UNO socket (TCP), not HTTP — their probes probe the socket.',
1327
+ },
1328
+ {
1329
+ name: 'conversionWorkers',
1330
+ type: 'number',
1331
+ required: false,
1332
+ description: 'LibreOffice conversion worker replicas (defaults to 1, only used with conversions)',
1333
+ },
1334
+ {
1335
+ name: 'secretsName',
1336
+ type: 'string',
1337
+ required: false,
1338
+ description: 'Name of an existing Secret holding `secretKey` and `smtpPassword`. Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions them. Plaintext values are not supported.',
1339
+ },
1340
+ {
1341
+ name: 'resources',
1342
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
1343
+ required: false,
1344
+ description: 'Requested app resources',
1345
+ },
1346
+ {
1347
+ name: 'tls',
1348
+ type: '{ secretName: string clusterIssuer: string }',
1349
+ required: false,
1350
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
1351
+ },
1352
+ ],
1353
+ example: "import { Platform } from '@r8s/recipes'\nimport { EuroOffice } from '@r8s/eurooffice'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <EuroOffice\n name=\"docs\"\n host=\"docs.example.com\"\n objectStorage={{\n endpoint: 'https://s3.internal.example.com',\n bucket: 'docs-blobs',\n credentialsSecret: 'docs-blobs-credentials',\n }}\n smtp={{ host: 'smtp.example.com', port: 587, from: 'no-reply@${env:MAIL_DOMAIN}' }}\n />\n </Platform>\n)",
1354
+ },
1355
+ {
1356
+ name: 'Paperclip',
1357
+ package: '@r8s/paperclip',
1358
+ category: 'Collaboration & Productivity',
1359
+ description: "Paperclip — Berget's agent platform (tasks, documents, agent orchestration)",
1360
+ props: [
1361
+ {
1362
+ name: 'name',
1363
+ type: 'string',
1364
+ required: false,
1365
+ description: "Resource name (defaults to 'paperclip')",
1366
+ },
1367
+ {
1368
+ name: 'namespace',
1369
+ type: 'string',
1370
+ required: false,
1371
+ description: "Kubernetes namespace (defaults to 'default')",
1372
+ },
1373
+ {
1374
+ name: 'version',
1375
+ type: 'string',
1376
+ required: false,
1377
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
1378
+ },
1379
+ {
1380
+ name: 'host',
1381
+ type: 'string',
1382
+ required: true,
1383
+ description: 'Public hostname for the web app and API (required)',
1384
+ },
1385
+ {
1386
+ name: 'replicas',
1387
+ type: 'number',
1388
+ required: false,
1389
+ description: 'Number of app replicas (defaults to 2)',
1390
+ },
1391
+ {
1392
+ name: 'dbStorage',
1393
+ type: 'string',
1394
+ required: false,
1395
+ description: "Storage size for the Postgres cluster (defaults to '10Gi')",
1396
+ },
1397
+ {
1398
+ name: 'websockets',
1399
+ type: 'boolean',
1400
+ required: false,
1401
+ description: 'Enable websockets for live task and agent updates (defaults to false)',
1402
+ },
1403
+ {
1404
+ name: 'agents',
1405
+ type: '{ /** Number of sandbox agent replicas (defaults to 2) */ sandboxReplicas?: number /** Sandbox container resources (defaults to requests 256Mi/250m, limits 2Gi/1000m) */ resources?: { requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } } }',
1406
+ required: false,
1407
+ description: 'Sandbox agent workers. Workers run the same image with a command override (paperclip agent --sandbox) and share the model API key and database credentials via secretKeyRef. They run with a hardened securityContext (non-root, no privilege escalation, RuntimeDefault seccomp, all capabilities dropped) by default.',
1408
+ },
1409
+ {
1410
+ name: 'secretsName',
1411
+ type: 'string',
1412
+ required: false,
1413
+ description: 'Name of an existing Secret containing key `modelApiKey`. Paperclip uses this key to call LLM providers on behalf of agents. Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions the key automatically. Plaintext keys are not supported.',
1414
+ },
1415
+ {
1416
+ name: 'resources',
1417
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
1418
+ required: false,
1419
+ description: 'Requested app resources',
1420
+ },
1421
+ {
1422
+ name: 'tls',
1423
+ type: '{ secretName: string clusterIssuer: string }',
1424
+ required: false,
1425
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
1426
+ },
1427
+ ],
1428
+ example: "import { Platform } from '@r8s/recipes'\nimport { Paperclip } from '@r8s/paperclip'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <Paperclip\n name=\"paperclip\"\n host=\"paperclip.example.com\"\n agents={{ sandboxReplicas: 3 }}\n />\n </Platform>\n)",
1429
+ },
1430
+ {
1431
+ name: 'Eneo',
1432
+ package: '@r8s/eneo',
1433
+ category: 'Collaboration & Productivity',
1434
+ description: 'Eneo — open AI platform from Sundsvall municipality (agent workspaces, assistants, document AI)',
1435
+ props: [
1436
+ {
1437
+ name: 'name',
1438
+ type: 'string',
1439
+ required: false,
1440
+ description: "Resource name (defaults to 'eneo')",
1441
+ },
1442
+ {
1443
+ name: 'namespace',
1444
+ type: 'string',
1445
+ required: false,
1446
+ description: "Kubernetes namespace (defaults to 'default')",
1447
+ },
1448
+ {
1449
+ name: 'version',
1450
+ type: 'string',
1451
+ required: false,
1452
+ description: "Container image tag (defaults to 'latest' — pin a version in production)",
1453
+ },
1454
+ {
1455
+ name: 'host',
1456
+ type: 'string',
1457
+ required: true,
1458
+ description: 'Public hostname for the Eneo web app (required)',
1459
+ },
1460
+ {
1461
+ name: 'replicas',
1462
+ type: 'number',
1463
+ required: false,
1464
+ description: 'Number of app replicas (defaults to 2 — scale freely, the app is stateless)',
1465
+ },
1466
+ {
1467
+ name: 'objectStorage',
1468
+ type: "{ /** S3 endpoint URL, e.g. https://s3.internal.example.com */ endpoint: string /** Bucket holding document corpora */ bucket: string /** Name of the Secret holding accessKey / secretKey */ credentialsSecret: string /** Region string for the S3 client (defaults to 'us-east-1') */ region?: string }",
1469
+ required: true,
1470
+ description: 'S3-compatible object storage for document corpora (RustFS in the platform). Required. Reference a bucket whose credentials live in a Secret provisioned by the secrets backend (keys: accessKey, secretKey) — never plaintext.',
1471
+ },
1472
+ {
1473
+ name: 'sso',
1474
+ type: '{ issuer: string clientId: string clientSecretRef: SecretRef scopes?: string }',
1475
+ required: false,
1476
+ description: 'OIDC SSO client — register Eneo as a client in Keycloak (the Auth recipe) and reference the client secret through the backend.',
1477
+ },
1478
+ {
1479
+ name: 'smtp',
1480
+ type: '{ /** SMTP server hostname, e.g. smtp.example.com */ host: string /** SMTP port (defaults to 587) */ port?: number /** From address for outgoing mail, e.g. no-reply@example.com */ from?: string }',
1481
+ required: false,
1482
+ description: 'Outgoing SMTP for invitations and notifications (mirror of the EuroOffice recipe). When set, SMTP_HOST / SMTP_PORT / SMTP_FROM are rendered as plain env and SMTP_PASSWORD is delivered via secretKeyRef from the `${name}-secrets` bundle (key: smtpPassword) — never plaintext. The bundle then requires the `smtpPassword` key as well; without `smtp` only `appSecret` is required from the bundle.',
1483
+ },
1484
+ {
1485
+ name: 'secretsName',
1486
+ type: 'string',
1487
+ required: false,
1488
+ description: 'Name of an existing Secret holding `appSecret` (and `smtpPassword` when `smtp` is set). Required unless a secrets backend (openbao/vault) is configured on the surrounding Platform — the backend then provisions them.',
1489
+ },
1490
+ {
1491
+ name: 'dbStorage',
1492
+ type: 'string',
1493
+ required: false,
1494
+ description: "Storage size for the Postgres cluster (defaults to '10Gi'). Document corpora live in object storage (`objectStorage`, S3/RustFS) — Eneo does not persist corpora on a local volume. A local corpus PVC (mounted volumes/sidecars on the app workload) is a v1.1 item.",
1495
+ },
1496
+ {
1497
+ name: 'resources',
1498
+ type: '{ requests?: { cpu?: string; memory?: string } limits?: { cpu?: string; memory?: string } }',
1499
+ required: false,
1500
+ description: 'Requested app resources',
1501
+ },
1502
+ {
1503
+ name: 'tls',
1504
+ type: '{ secretName: string clusterIssuer: string }',
1505
+ required: false,
1506
+ description: 'TLS configuration (defaults to letsencrypt-prod cluster issuer)',
1507
+ },
1508
+ ],
1509
+ example: "import { Platform } from '@r8s/recipes'\nimport { Eneo } from '@r8s/eneo'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <Eneo\n name=\"eneo\"\n host=\"eneo.example.com\"\n objectStorage={{\n endpoint: 'https://s3.internal.example.com',\n bucket: 'eneo-corpora',\n credentialsSecret: 'eneo-object-storage',\n }}\n />\n </Platform>\n)",
1510
+ },
1511
+ {
1512
+ name: 'Matrix',
1513
+ package: '@r8s/matrix',
1514
+ category: 'Collaboration & Productivity',
1515
+ description: 'Matrix — full Element Server Suite (Synapse, MAS with OIDC SSO, Element Web/Admin, LiveKit SFU) with production HA defaults',
1516
+ props: [
1517
+ {
1518
+ name: 'name',
1519
+ type: 'string',
1520
+ required: false,
1521
+ description: "Resource name (defaults to 'matrix')",
1522
+ },
1523
+ {
1524
+ name: 'namespace',
1525
+ type: 'string',
1526
+ required: false,
1527
+ description: 'Kubernetes namespace (inherited from Platform when omitted)',
1528
+ },
1529
+ {
1530
+ name: 'domain',
1531
+ type: 'string',
1532
+ required: true,
1533
+ description: 'Base domain — derives the five public hosts (web: element., synapse: matrix., admin: element-admin., account: matrix-account., rtc: matrix-rtc.)',
1534
+ },
1535
+ {
1536
+ name: 'hosts',
1537
+ type: "Partial<Record<'web' | 'synapse' | 'admin' | 'account' | 'rtc', string>>",
1538
+ required: false,
1539
+ description: 'Per-hostname overrides for the five ingress hosts',
1540
+ },
1541
+ {
1542
+ name: 'serverName',
1543
+ type: 'string',
1544
+ required: false,
1545
+ description: 'Matrix server name used in user IDs (@user:serverName). Defaults to domain.',
1546
+ },
1547
+ {
1548
+ name: 'sso',
1549
+ type: '{ issuer: string clientId: string clientSecretRef?: string humanName?: string scope?: string }',
1550
+ required: false,
1551
+ description: 'OIDC SSO upstream for MAS (Keycloak from the Auth recipe). Disables local password login when set. Requires a secrets backend or clientSecretRef — never inline secrets.',
1552
+ },
1553
+ {
1554
+ name: 'database',
1555
+ type: '{ replicas?: number storage?: string storageClass?: string backup?: { destinationPath: string endpointURL: string credentialsSecret?: string retention?: string schedule?: string } | null }',
1556
+ required: false,
1557
+ description: 'Per-database sizing for the two CNPG clusters (synapse-db, mas-db). Backup is explicit opt-in: barman object store with 30d retention + ScheduledBackup.',
1558
+ },
1559
+ {
1560
+ name: 'rtc',
1561
+ type: '{ enabled?: boolean manualIP?: string turnPort?: number stunServers?: string[] sfuVersion?: string }',
1562
+ required: false,
1563
+ description: 'LiveKit SFU for Element Call. Renders a combined LoadBalancer service (numeric UDP targetPort 30002 — upstream chart bug workaround). Disable for text-only deployments.',
1564
+ },
1565
+ {
1566
+ name: 'appservices',
1567
+ type: '({ name: string; registration: Record<string, unknown> } | { name: string; secretRef: string; key?: string })[]',
1568
+ required: false,
1569
+ description: 'Appservice registrations (hookshot, bots). Inline registration renders as a Secret (never ConfigMap — use token placeholders or expect the guardrail to flag live values); secretRef mounts an existing Secret holding registration.yaml.',
1570
+ },
1571
+ {
1572
+ name: 'version',
1573
+ type: '{ synapse?: string mas?: string web?: string admin?: string sfu?: string }',
1574
+ required: false,
1575
+ description: 'Per-component image tags. Defaults are pinned for known upstream regressions (web v1.12.15 / sfu v1.10.1) — override only with intent.',
1576
+ },
1577
+ {
1578
+ name: 'urlPreview',
1579
+ type: 'boolean',
1580
+ required: false,
1581
+ description: 'Set false to disable the SSRF-hardened url_preview_ip_range_blacklist preset (default: enabled)',
1582
+ },
1583
+ ],
1584
+ example: "import { Platform } from '@r8s/recipes'\nimport { Matrix } from '@r8s/matrix'\n\nexport default (\n <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n <Matrix\n domain=\"example.com\"\n sso={{\n issuer: 'https://keycloak.example.com/realms/berget',\n clientId: 'matrix',\n }}\n database={{\n backup: {\n destinationPath: 's3://backups/matrix-cnpg',\n endpointURL: 'https://s3.example.com',\n },\n }}\n />\n </Platform>\n)",
1585
+ },
511
1586
  ];
512
1587
  // Auth sub-components (from @r8s/recipes/auth)
513
1588
  exports.authComponents = [