-
Notifications
You must be signed in to change notification settings - Fork 35
Expand file tree
/
Copy pathopenhands.yaml
More file actions
996 lines (977 loc) · 64.8 KB
/
Copy pathopenhands.yaml
File metadata and controls
996 lines (977 loc) · 64.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
apiVersion: kots.io/v1beta2
kind: HelmChart
metadata:
name: openhands
spec:
chart:
name: openhands
releaseName: openhands
namespace: openhands
weight: 10
helmUpgradeFlags: ["--wait", "--timeout", "600s", "--force-conflicts"]
values:
global:
imageRegistry: 'images.r9.all-hands.dev'
imagePullSecrets:
- '{{repl ImagePullSecretName }}'
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/enterprise-server'
tag: 'sha-be6f446'
imagePullSecrets:
- name: '{{repl ImagePullSecretName }}'
env:
# Default model for new users/orgs: the first non-empty line of the
# "Anthropic Models" KOTS textarea (anthropic is the base provider; the
# other providers' optionalValues blocks below override this env var).
# Falls back to claude-sonnet-4-5-20250929 — today's hardcoded default
# and the textarea's prefilled first line — if the textarea resolves
# empty (e.g. while another provider is selected).
LITELLM_DEFAULT_MODEL: 'litellm_proxy/repl{{ $first := "" }}repl{{ range $raw := splitList "\n" (ConfigOption "anthropic_models" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not $first) }}repl{{ $first = $m }}repl{{ end }}repl{{ end }}repl{{ $first | default "claude-sonnet-4-5-20250929" }}'
REQUIRE_PAYMENT: 0
ENABLE_BILLING: false
OH_WEB_CLIENT_FEATURE_FLAGS_ENABLE_BILLING: false
OH_WEB_CLIENT_FEATURE_FLAGS_HIDE_BILLING_PAGE: true
# Frontend feature flags must be set via the structured OH_WEB_CLIENT_FEATURE_FLAGS_*
# form. Once any of these is present, from_env(AppServerConfig, 'OH') builds
# feature_flags from them and the plain ENABLE_JIRA_DC env (set in _env.yaml) is
# ignored, so the flag falls back to the model default (false) and the Jira DC
# integration card never renders. Mirror jira_data_center_enabled here.
OH_WEB_CLIENT_FEATURE_FLAGS_ENABLE_JIRA_DC: 'repl{{ ConfigOptionEquals "jira_data_center_enabled" "1" }}'
LOG_LEVEL: 'repl{{ ConfigOption "log_level" }}'
# The chart's fallback for LAMINAR_WEB_HOST is laminar.<ingress.host>, but
# Replicated installs expose Laminar at the analytics hostname. Set it
# unconditionally (not gated on analytics_enabled) so the Keycloak realm
# redirect URIs stay on the analytics domain either way.
LAMINAR_WEB_HOST: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}analytics.app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "analytics_hostname"}}{{repl end}}'
OH_AGENT_SERVER_ENV: '{"SSL_CERT_FILE": "/etc/openhands/ca-bundle/ca-bundle.crt", "REQUESTS_CA_BUNDLE": "/etc/openhands/ca-bundle/ca-bundle.crt", "GIT_SSL_CAINFO": "/etc/openhands/ca-bundle/ca-bundle.crt", "CURL_CA_BUNDLE": "/etc/openhands/ca-bundle/ca-bundle.crt", "NODE_EXTRA_CA_CERTS": "/etc/openhands/ca-bundle/ca-bundle.crt"}'
OPENHANDS_DEFAULT_ORG_ENABLED: 'repl{{ ConfigOptionEquals "default_openhands_org_enabled" "1" }}'
OPENHANDS_DEFAULT_ORG_AUTO_ADD_USERS: 'repl{{ ConfigOptionEquals "default_openhands_org_auto_add_users" "1" }}'
# Hiding personal workspaces needs both forms: the structured flag drives
# the web client (plain envs are ignored once any OH_WEB_CLIENT_FEATURE_FLAGS_*
# is set — see the ENABLE_JIRA_DC note above), and the plain env drives the
# server-side default-org bootstrap that moves users off personal workspaces
# on login. Both repeat the checkbox's `when` gating (default org enabled +
# auto-add on) because KOTS keeps a hidden item's stored value: unchecking
# auto-add or disabling the default org must also switch hiding off, or users
# would lose their personal workspace with no auto-membership to land in.
OH_WEB_CLIENT_FEATURE_FLAGS_HIDE_PERSONAL_WORKSPACES: 'repl{{ and (ConfigOptionEquals "default_openhands_org_enabled" "1") (ConfigOptionEquals "default_openhands_org_auto_add_users" "1") (ConfigOptionEquals "default_openhands_org_hide_personal_workspaces" "1") }}'
HIDE_PERSONAL_WORKSPACES: 'repl{{ and (ConfigOptionEquals "default_openhands_org_enabled" "1") (ConfigOptionEquals "default_openhands_org_auto_add_users" "1") (ConfigOptionEquals "default_openhands_org_hide_personal_workspaces" "1") }}'
# BYOK: whether users may configure their own LLM providers/keys in
# their settings, in addition to the admin-managed proxy models.
# Enterprise reads the FEATURE_FLAGS-prefixed name; bare name kept for OSS.
OH_WEB_CLIENT_FEATURE_FLAGS_ALLOW_USER_LLM_CONFIGURATION: 'repl{{ ConfigOptionEquals "allow_user_llm_configuration" "1" }}'
OH_ALLOW_USER_LLM_CONFIGURATION: 'repl{{ ConfigOptionEquals "allow_user_llm_configuration" "1" }}'
ingress:
enabled: true
host: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "app_hostname"}}{{repl end}}'
class: traefik
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
certificate:
enabled: false
tlsStore:
enabled: true
name: default
secretName: openhands-tls
tls:
enabled: true
env: '{{repl Namespace }}'
runtime:
image:
# Defaults to the Replicated-proxied agent-server image. When
# custom_sandbox_image_enabled=1, an admin-supplied repository/tag
# takes over — both for new sandboxes (via AGENT_SERVER_IMAGE_*
# env on the openhands server) and for the warm pool below.
repository: '{{repl if ConfigOptionEquals "custom_sandbox_image_enabled" "1"}}{{repl ConfigOption "custom_sandbox_image_repository"}}{{repl else}}images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/agent-server{{repl end}}'
tag: '{{repl if ConfigOptionEquals "custom_sandbox_image_enabled" "1"}}{{repl ConfigOption "custom_sandbox_image_tag"}}{{repl else}}1.28.0-python{{repl end}}'
keycloak:
enabled: true
url: 'http://keycloak'
ssoSessionIdleTimeout: 'repl{{ ConfigOption "keycloak_sso_session_idle_timeout" }}'
ssoSessionMaxLifespan: 'repl{{ ConfigOption "keycloak_sso_session_max_lifespan" }}'
resourcesPreset: none
resources:
requests:
cpu: 500m
memory: 512Mi
ephemeral-storage: 50Mi
limits:
cpu: "2"
memory: 2Gi
ephemeral-storage: 2Gi
ingress:
enabled: true
ingressClassName: traefik
hostname: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}auth.app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "auth_hostname"}}{{repl end}}'
tls: true
annotations:
nginx.ingress.kubernetes.io/upstream-vhost: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}auth.app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "auth_hostname"}}{{repl end}}'
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
secrets:
- name: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}auth.app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "auth_hostname"}}{{repl end}}-tls'
key: |
{{repl ConfigOptionData "tls_private_key" | nindent 14}}
certificate: |
{{repl ConfigOptionData "tls_certificate" | nindent 14}}
externalDatabase:
host: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_host"}}{{repl else}}oh-main-postgresql{{repl end}}'
port: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_port"}}{{repl else}}5432{{repl end}}'
sslMode: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_ssl_mode"}}{{repl else}}prefer{{repl end}}'
existingSecret: postgres-password
existingSecretUserKey: username
existingSecretPasswordKey: password
image:
repository: 'proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/keycloak'
keycloakConfigCli:
image:
repository: 'proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/keycloak-config-cli'
waitForDb:
image: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/postgresql:latest'
# caBundle wiring: mount the trust-manager Bundle ConfigMap and point
# Keycloak's truststore at the merged PEM. Repeats the chart's default
# KC_* env vars because helm value merging replaces arrays — adding
# only KC_TRUSTSTORE_PATHS here would drop the others.
extraEnvVars:
- name: KC_FEATURES
value: token-exchange,admin-fine-grained-authz
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_PROXY_HEADERS
value: "xforwarded"
- name: KC_SPI_LOGIN_PROTOCOL_OPENID_CONNECT_LEGACY_LOGOUT_REDIRECT_URI
value: "true"
- name: KC_TRUSTSTORE_PATHS
value: /etc/openhands/ca-bundle/ca-bundle.crt
- name: KC_DB_URL_PROPERTIES
value: 'sslmode={{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_ssl_mode"}}{{repl else}}prefer{{repl end}}'
extraVolumes:
- name: openhands-ca-bundle
configMap:
name: openhands-ca-bundle
extraVolumeMounts:
- name: openhands-ca-bundle
mountPath: /etc/openhands/ca-bundle
readOnly: true
litellm:
url: 'http://openhands-litellm:4000'
litellm-helm:
enabled: true
db:
# TODO: should the fallback be openhands-postgresql here?
endpoint: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_host"}}{{repl else}}oh-main-postgresql{{repl end}}'
url: 'postgresql://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(DATABASE_HOST):{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_port"}}{{repl else}}5432{{repl end}}/$(DATABASE_NAME)?sslmode={{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_ssl_mode"}}{{repl else}}prefer{{repl end}}'
secret:
name: postgres-password
usernameKey: username
passwordKey: password
# Migrations run automatically when litellm starts. This disables running them separately as a job.
migrationJob:
enabled: false
args:
- --config
- /etc/litellm/config.yaml
- --use_v2_migration_resolver
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/berriai/litellm-database'
tag: "1.84.1" # bare semver, no "v" prefix or "-stable" suffix (format used by BerriAI for 1.84.x+ stable images)
imagePullSecrets:
- name: '{{repl ImagePullSecretName }}'
environmentSecrets:
- litellm-env-secrets
podAnnotations:
checksum/litellm-credentials: 'repl{{ sha256sum (printf "%s|%s|%s" (ConfigOption "litellm_api_key") (ConfigOption "litellm_admin_password") (ConfigOption "litellm_salt_key")) }}'
# caBundle wiring: mount the trust-manager Bundle ConfigMap and point
# the Python SSL stack (httpx + requests) at the merged PEM. envVars
# is a map (merges with proxy/vertex blocks), but volumes/volumeMounts
# are arrays — the vertex optionalValues block below repeats the
# ca-bundle entry alongside the google-credentials entry so its array
# replacement preserves both.
envVars:
SSL_CERT_FILE: /etc/openhands/ca-bundle/ca-bundle.crt
REQUESTS_CA_BUNDLE: /etc/openhands/ca-bundle/ca-bundle.crt
UI_USERNAME: admin
# general_settings.store_model_in_db gates the management API, but
# the startup loader that re-adds DB-stored models to the router
# after a pod restart only honors this environment variable.
# Verified on litellm-database:1.84.1 — without it, dashboard-added
# models persist in the DB but vanish from routing on restart.
STORE_MODEL_IN_DB: "True"
volumes:
- name: openhands-ca-bundle
configMap:
name: openhands-ca-bundle
volumeMounts:
- name: openhands-ca-bundle
mountPath: /etc/openhands/ca-bundle
readOnly: true
ingress:
enabled: true
className: traefik
hosts:
- host: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}llm-proxy.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "llm_proxy_hostname"}}{{repl end}}'
paths:
- path: /
pathType: Prefix
tls:
- secretName: openhands-tls
hosts:
- '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}llm-proxy.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "llm_proxy_hostname"}}{{repl end}}'
proxy_config:
environment_variables:
OR_APP_NAME: "OpenHands"
OR_SITE_URL: "https://docs.all-hands.dev"
general_settings:
forward_client_headers_to_llm_api: repl{{ ConfigOptionEquals "litellm_forward_client_headers" "1" }}
# Let admins add/manage models at runtime through the LiteLLM
# dashboard. DB-stored models are encrypted with LITELLM_SALT_KEY,
# merge with (never replace) the config-rendered model_list below,
# hot-reload roughly every 30s, and survive KOTS upgrades. No
# optionalValues block overrides general_settings, and
# recursiveMerge merges maps, so this stays set for every provider
# selection.
store_model_in_db: true
# One <id> -> anthropic/<id> entry per non-empty line of the
# "Anthropic Models" KOTS textarea (trimmed, blanks skipped), emitted
# as JSON. KOTS recursiveMerge REPLACES arrays, so the provider
# optionalValues blocks below each carry their own complete
# model_list. The textarea is prefilled with claude-sonnet-4-5-20250929
# and claude-opus-4-7 — exactly the two routes this list used to
# hardcode — so upgraded installs keep serving the same route names
# and existing user/org settings (no aliases needed; an admin who
# later removes those lines orphans settings still referencing them —
# accepted, the app shows an unavailable badge). claude-opus-4-7 is
# retained in the prefill for backward compatibility with existing
# OpenHands installations that route openhands/claude-opus-4-7 through
# the proxy. LiteLLM 1.84.1 is the stable release containing the
# thinking translation fix from
# https://github.com/BerriAI/litellm/pull/27074.
# If the textarea resolves empty (e.g. while another provider is
# selected — this base block renders unconditionally), fall back to
# those same two routes so the proxy always has a non-empty list.
model_list: repl{{ $models := list }}repl{{ $seen := dict }}repl{{ range $raw := splitList "\n" (ConfigOption "anthropic_models" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not (hasKey $seen $m)) }}repl{{ $seen = set $seen $m true }}repl{{ $models = append $models (dict "model_name" $m "litellm_params" (dict "model" (printf "anthropic/%s" $m) "api_key" "os.environ/ANTHROPIC_API_KEY")) }}repl{{ end }}repl{{ end }}repl{{ if not $models }}repl{{ $models = list (dict "model_name" "claude-sonnet-4-5-20250929" "litellm_params" (dict "model" "anthropic/claude-sonnet-4-5-20250929" "api_key" "os.environ/ANTHROPIC_API_KEY")) (dict "model_name" "claude-opus-4-7" "litellm_params" (dict "model" "anthropic/claude-opus-4-7" "api_key" "os.environ/ANTHROPIC_API_KEY")) }}repl{{ end }}repl{{ $models | mustToJson }}
runtime-api:
enabled: true
replicaCount: 1
autoscaling:
enabled: false
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/runtime-api'
tag: 'sha-8919a43'
imagePullSecrets:
- name: '{{repl ImagePullSecretName }}'
ingress:
enabled: true
className: traefik
host: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}runtime-api.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "runtime_api_hostname"}}{{repl end}}'
tls: true
tlsSecretName: openhands-tls
# Suppress the chart's default letsencrypt annotation: Replicated
# installs use a manually-uploaded TLS cert (openhands-tls), and the
# cluster-issuer annotation triggers cert-manager to take SSA
# ownership of the Secret, blocking subsequent updates to it.
annotations:
cert-manager.io/cluster-issuer: null
# caBundle wiring: mount the trust-manager Bundle ConfigMap and point
# the Python TLS stack (httpx + requests) at the merged PEM. Required
# so runtimes.check_alive_with_ingress can verify the runtime ingress
# cert when the user uploads a custom CA — otherwise it silently fails
# TLS and runtimes get stuck in `starting`.
caBundle:
enabled: true
runtimeInSameCluster: true
cleanup:
idle_seconds: repl{{ ConfigOption "sandbox_idle_seconds" }}
dead_seconds: repl{{ ConfigOption "sandbox_dead_seconds" }}
env:
DB_HOST: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_host"}}{{repl else}}oh-main-postgresql{{repl end}}'
DB_PORT: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_port"}}{{repl else}}5432{{repl end}}'
DB_SSL_MODE: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_ssl_mode"}}{{repl else}}prefer{{repl end}}'
PGSSLMODE: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_ssl_mode"}}{{repl else}}prefer{{repl end}}'
DB_USER: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_username"}}{{repl else}}postgres{{repl end}}'
DB_NAME: "runtime_api_db"
RUNTIME_BASE_URL: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}runtime.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "runtime_base_hostname"}}{{repl end}}'
RUNTIME_IMAGE_PULL_SECRETS: '{{repl if and (ConfigOptionEquals "custom_sandbox_image_enabled" "1") (ne (ConfigOption "custom_sandbox_image_registry_server") "") (ne (ConfigOption "custom_sandbox_image_registry_username") "") (ne (ConfigOption "custom_sandbox_image_registry_password") "") }}sandbox-image-pull-secret{{repl else}}{{repl ImagePullSecretName }}{{repl end}}'
STORAGE_CLASS: "openebs-hostpath"
INGRESS_CLASS: "traefik"
RUNTIME_CLASS: ""
RUNTIME_DISABLE_SSL: "false"
PERSISTENT_STORAGE_SIZE: 'repl{{ ConfigOption "sandbox_storage_size" }}'
MEMORY_REQUEST: 'repl{{ ConfigOption "sandbox_memory_request" }}'
MEMORY_LIMIT: 'repl{{ ConfigOption "sandbox_memory_limit" }}'
CPU_REQUEST: 'repl{{ ConfigOption "sandbox_cpu_request" }}'
CPU_LIMIT: 'repl{{ ConfigOption "sandbox_cpu_limit" }}'
LOG_LEVEL: 'repl{{ ConfigOption "log_level" }}'
ADDITIONAL_CONFIGMAPS: "openhands-ca-bundle:/etc/openhands/ca-bundle"
ADDITIONAL_HOST_PATHS: 'repl{{ ConfigOption "sandbox_additional_host_paths" }}'
SANDBOX_KVM_ENABLED: 'repl{{ ConfigOptionEquals "sandbox_kvm_enabled" "1" }}'
KVM_DEVICE_RESOURCE: "smarter-devices/kvm"
kvm:
enabled: repl{{ ConfigOptionEquals "sandbox_kvm_enabled" "1" }}
resourceName: smarter-devices/kvm
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/smarter-project/smarter-device-manager'
tag: v1.20.11
initImage:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/library/busybox'
tag: "1.36"
warmRuntimes:
enabled: true
count: repl{{ ConfigOption "sandbox_warm_runtime_count" }}
configs:
- name: default
image: '{{repl if ConfigOptionEquals "custom_sandbox_image_enabled" "1"}}{{repl ConfigOption "custom_sandbox_image_repository"}}:{{repl ConfigOption "custom_sandbox_image_tag"}}{{repl else}}images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/agent-server:1.28.0-python{{repl end}}'
working_dir: "/workspace"
environment:
LOG_JSON: "1"
LOG_JSON_LEVEL_KEY: "severity"
OH_ALLOW_CORS_ORIGINS_0: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "app_hostname"}}{{repl end}}'
OH_BASH_EVENTS_DIR: "/workspace/bash_events"
OH_CONVERSATIONS_PATH: "/workspace/conversations"
OH_ENABLE_VNC: "0"
OH_VSCODE_PORT: "60001"
OH_WEBHOOKS_0_BASE_URL: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "app_hostname"}}{{repl end}}/api/v1/webhooks'
OPENVSCODE_SERVER_ROOT: "/openhands/.openvscode-server"
VSCODE_PORT: "60001"
WORKER_1: "12000"
WORKER_2: "12001"
CURL_CA_BUNDLE: "/etc/openhands/ca-bundle/ca-bundle.crt"
GIT_SSL_CAINFO: "/etc/openhands/ca-bundle/ca-bundle.crt"
NODE_EXTRA_CA_CERTS: "/etc/openhands/ca-bundle/ca-bundle.crt"
REQUESTS_CA_BUNDLE: "/etc/openhands/ca-bundle/ca-bundle.crt"
SSL_CERT_FILE: "/etc/openhands/ca-bundle/ca-bundle.crt"
LMNR_BASE_URL: &lmnr_base_url '{{repl if ConfigOptionEquals "analytics_enabled" "1" }}http://laminar-app-server-service{{repl end }}'
LMNR_FORCE_HTTP: &lmnr_force_http '{{repl if ConfigOptionEquals "analytics_enabled" "1" }}true{{repl end }}'
LMNR_HTTP_PORT: &lmnr_http_port '{{repl if ConfigOptionEquals "analytics_enabled" "1" }}8000{{repl end }}'
LMNR_PROJECT_API_KEY: &lmnr_project_api_key '{{repl if ConfigOptionEquals "analytics_enabled" "1" }}{{repl ConfigOption "laminar_project_api_key"}}{{repl end }}'
command:
- /usr/local/bin/openhands-agent-server
- "--port"
- "60000"
run_as_user: 10001
run_as_group: 10001
fs_group: 10001
ingressBase:
enabled: true
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: base-ingress
labels:
app: runtime-api
annotations:
kubernetes.io/ingress.class: traefik
sandbox:
apiHostname: 'http://openhands-runtime-api:5000'
postgresql:
enabled: repl{{ ConfigOptionEquals "postgres_type" "embedded_postgres" }}
redis:
enabled: true
image:
repository: 'proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/redis'
auth:
password: '{{repl ConfigOption "redis_password" | Base64Encode }}'
filestore:
ephemeral: true
minio:
persistence:
enabled: true
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/quay.io/minio/minio'
imagePullSecrets:
- name: '{{repl ImagePullSecretName }}'
# sha256 of every secret (password) config value. Changing any secret in the
# KOTS config changes this hash, which changes the openhands pod-template
# annotation (checksum/config-secrets) and forces a rollout so secret-backed
# env vars are reloaded. Keep this in sync with the password fields in
# config.yaml; CI (check-secret-checksum) fails if a password field is missing here.
# Grouped in order: LLM provider keys; app/infra secrets (admin, postgres,
# redis, jwt, keycloak, litellm, sandbox, plugin-directory, automation); then
# auth/integration secrets (bitbucket DC, jira DC, github, gitlab, slack, laminar).
secretsChecksum: 'repl{{ sha256sum (printf "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" (ConfigOption "anthropic_api_key") (ConfigOption "openai_api_key") (ConfigOption "google_gemini_api_key") (ConfigOption "deepseek_api_key") (ConfigOption "mistral_api_key") (ConfigOption "azure_api_key") (ConfigOption "azure_client_secret") (ConfigOption "groq_api_key") (ConfigOption "openrouter_api_key") (ConfigOption "aws_secret_access_key") (ConfigOption "custom_api_key") (ConfigOption "admin_password") (ConfigOption "postgres_password") (ConfigOption "redis_password") (ConfigOption "jwt_secret") (ConfigOption "keycloak_admin_password") (ConfigOption "keycloak_client_secret") (ConfigOption "litellm_api_key") (ConfigOption "litellm_admin_password") (ConfigOption "litellm_salt_key") (ConfigOption "default_api_key") (ConfigOption "sandbox_api_key") (ConfigOption "keycloak_smtp_password") (ConfigOption "plugin_directory_identity_shared_secret") (ConfigOption "plugin_directory_session_secret") (ConfigOption "automation_service_key") (ConfigOption "automation_webhook_secret") (ConfigOption "bitbucket_data_center_client_secret") (ConfigOption "bitbucket_data_center_bot_token") (ConfigOption "azure_devops_client_secret") (ConfigOption "jira_data_center_client_secret") (ConfigOption "jira_data_center_service_account_email") (ConfigOption "jira_data_center_service_account_pat") (ConfigOption "github_oauth_client_secret") (ConfigOption "github_app_webhook_secret") (ConfigOption "gitlab_oauth_client_secret") (ConfigOption "slack_client_secret") (ConfigOption "slack_signing_secret") (ConfigOption "external_postgres_password") (ConfigOption "custom_sandbox_image_registry_password") (ConfigOption "laminar_project_api_key")) }}'
bitbucketDataCenter:
enabled: repl{{ ConfigOptionEquals "bitbucket_data_center_auth_enabled" "1" }}
host: 'repl{{ ConfigOption "bitbucket_data_center_domain" }}'
botUsername: 'repl{{ ConfigOption "bitbucket_data_center_bot_username" }}'
azureDevOps:
enabled: repl{{ ConfigOptionEquals "azure_devops_auth_enabled" "1" }}
tenantId: 'repl{{ ConfigOption "azure_devops_tenant_id" }}'
organization: 'repl{{ ConfigOption "azure_devops_organization" }}'
jiraDc:
enabled: repl{{ ConfigOptionEquals "jira_data_center_enabled" "1" }}
linkMethod: 'repl{{ ConfigOption "jira_data_center_link_method" }}'
github:
enabled: repl{{ ConfigOptionEquals "github_auth_enabled" "1" }}
gitlab:
enabled: repl{{ ConfigOptionEquals "gitlab_auth_enabled" "1" }}
host: 'repl{{ ConfigOption "gitlab_host" }}'
slack:
enabled: repl{{ ConfigOptionEquals "slack_enabled" "1" }}
replicated:
enabled: true
redisPassword: 'repl{{ ConfigOption "redis_password" }}'
postgresUsername: 'repl{{ ConfigOption "postgres_username" }}'
postgresPassword: 'repl{{ ConfigOption "postgres_password" }}'
postgresDatabase: 'repl{{ ConfigOption "postgres_database" }}'
image:
registry: images.r9.all-hands.dev
repository: library/replicated-sdk-image
crdCheck:
enabled: true
crds:
- certificates.cert-manager.io
- issuers.cert-manager.io
- clusterissuers.cert-manager.io
- bundles.trust.cert-manager.io
- ingressroutes.traefik.io
- middlewares.traefik.io
- tlsstores.traefik.io
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/rancher/kubectl'
imagePullSecrets:
- name: '{{repl ImagePullSecretName }}'
caBundle:
enabled: true
userCA: |
{{repl ConfigOptionData "tls_ca_certificate" | nindent 8 }}
optionalValues:
- when: '{{repl ConfigOptionEquals "postgres_type" "embedded_postgres" }}'
recursiveMerge: true
values:
databaseMigrations:
createDatabases: true
runtime-api:
databaseMigrations:
createDatabases: true
plugin-directory:
databaseMigrations:
createDatabases: true
postgresql:
enabled: true
auth:
username: '{{repl ConfigOption "postgres_username"}}'
password: '{{repl ConfigOption "postgres_password" | Base64Encode }}'
postgresPassword: '{{repl ConfigOption "postgres_password" | Base64Encode }}'
database: '{{repl ConfigOption "postgres_database"}}'
persistence:
enabled: true
- when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres" }}'
recursiveMerge: true
values:
databaseMigrations:
createDatabases: repl{{ ConfigOptionEquals "external_postgres_create_databases" "1" }}
postgresql:
enabled: false
externalDatabase:
host: '{{repl ConfigOption "external_postgres_host"}}'
port: '{{repl ConfigOption "external_postgres_port"}}'
sslMode: '{{repl ConfigOption "external_postgres_ssl_mode"}}'
database: '{{repl ConfigOption "external_postgres_database"}}'
username: '{{repl ConfigOption "external_postgres_username"}}'
password: '{{repl ConfigOption "external_postgres_password"}}'
# existingSecret: postgres-password
# existingSecretUserKey: username
# existingSecretPasswordKey: password
keycloak:
externalDatabase:
port: '{{repl ConfigOption "external_postgres_port"}}'
sslMode: '{{repl ConfigOption "external_postgres_ssl_mode"}}'
database: '{{repl ConfigOption "external_postgres_keycloak_database"}}'
litellm-helm:
db:
url: 'postgresql://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(DATABASE_HOST):{{repl ConfigOption "external_postgres_port"}}/$(DATABASE_NAME)?sslmode={{repl ConfigOption "external_postgres_ssl_mode"}}'
database: '{{repl ConfigOption "external_postgres_litellm_database"}}'
runtime-api:
databaseMigrations:
createDatabases: repl{{ ConfigOptionEquals "external_postgres_create_databases" "1" }}
env:
DB_PORT: '{{repl ConfigOption "external_postgres_port"}}'
DB_SSL_MODE: '{{repl ConfigOption "external_postgres_ssl_mode"}}'
PGSSLMODE: '{{repl ConfigOption "external_postgres_ssl_mode"}}'
DB_NAME: '{{repl ConfigOption "external_postgres_runtime_api_database"}}'
plugin-directory:
databaseMigrations:
createDatabases: repl{{ ConfigOptionEquals "external_postgres_create_databases" "1" }}
database:
sslMode: '{{repl ConfigOption "external_postgres_ssl_mode"}}'
- when: '{{repl ConfigOptionEquals "github_auth_enabled" "1" }}'
recursiveMerge: true
values:
env:
ENABLE_V1_GITHUB_RESOLVER: "true"
githubApp:
enabled: true
appId: '{{repl ConfigOption "github_app_id"}}'
clientId: '{{repl ConfigOption "github_oauth_client_id"}}'
clientSecret: '{{repl ConfigOption "github_oauth_client_secret"}}'
webhookSecret: '{{repl ConfigOption "github_app_webhook_secret"}}'
privateKey: |
{{repl ConfigOptionData "github_app_private_key" | nindent 12}}
- when: '{{repl ConfigOptionEquals "slack_enabled" "1" }}'
recursiveMerge: true
values:
env:
ENABLE_V1_SLACK_RESOLVER: "true"
slack:
clientId: 'repl{{ ConfigOption "slack_client_id" }}'
- when: '{{repl ConfigOptionEquals "gitlab_auth_enabled" "1" }}'
recursiveMerge: true
values:
env:
ENABLE_V1_GITLAB_RESOLVER: "true"
gitlabWebhookInstallation:
enabled: true
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "200m"
- when: '{{repl ConfigOptionEquals "automations_enabled" "1" }}'
recursiveMerge: true
values:
automationServiceKey:
enabled: true
existingSecret: automation-service-key
secretKey: automation-service-key
automationService:
url: http://automation/api/automation
eventForwardingEnabled: true
automationWebhookSecret:
enabled: true
existingSecret: automation-webhook-secret
secretKey: webhook-secret
automation:
enabled: true
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/automation'
imagePullSecrets:
- name: '{{repl ImagePullSecretName }}'
openhandsApiUrl: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}https://app.{{repl ConfigOption "base_domain"}}{{repl else}}https://{{repl ConfigOption "app_hostname"}}{{repl end}}'
automationBaseUrl: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}https://app.{{repl ConfigOption "base_domain"}}{{repl else}}https://{{repl ConfigOption "app_hostname"}}{{repl end}}'
filestore:
type: local
env:
LOCAL_STORAGE_PATH: /tmp/automation
database:
createDatabaseUser: true
host: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_host"}}{{repl else}}openhands-postgresql{{repl end}}'
port: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_port"}}{{repl else}}5432{{repl end}}'
sslMode: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_ssl_mode"}}{{repl else}}prefer{{repl end}}'
superuserName: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_username"}}{{repl else}}postgres{{repl end}}'
- when: '{{repl ConfigOptionEquals "postgres_type" "embedded_postgres" }}'
recursiveMerge: true
values:
postgresql:
image:
repository: 'proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/postgresql'
keycloak:
postgresql:
image:
repository: 'proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/postgresql'
- when: '{{repl HasLocalRegistry }}'
recursiveMerge: true
values:
global:
imageRegistry: '{{repl LocalRegistryHost }}'
image:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/deploy'
runtime:
image:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/agent-server'
tag: '1.28.0-python'
warmRuntimes:
configs:
- name: default
image: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/agent-server:1.28.0-python'
working_dir: "/workspace"
environment:
LOG_JSON: "1"
LOG_JSON_LEVEL_KEY: "severity"
OH_ALLOW_CORS_ORIGINS_0: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "app_hostname"}}{{repl end}}'
OH_BASH_EVENTS_DIR: "/workspace/bash_events"
OH_CONVERSATIONS_PATH: "/workspace/conversations"
OH_ENABLE_VNC: "0"
OH_VSCODE_PORT: "60001"
OH_WEBHOOKS_0_BASE_URL: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "app_hostname"}}{{repl end}}/api/v1/webhooks'
OPENVSCODE_SERVER_ROOT: "/openhands/.openvscode-server"
VSCODE_PORT: "60001"
WORKER_1: "12000"
WORKER_2: "12001"
CURL_CA_BUNDLE: "/etc/openhands/ca-bundle/ca-bundle.crt"
GIT_SSL_CAINFO: "/etc/openhands/ca-bundle/ca-bundle.crt"
NODE_EXTRA_CA_CERTS: "/etc/openhands/ca-bundle/ca-bundle.crt"
REQUESTS_CA_BUNDLE: "/etc/openhands/ca-bundle/ca-bundle.crt"
SSL_CERT_FILE: "/etc/openhands/ca-bundle/ca-bundle.crt"
LMNR_BASE_URL: *lmnr_base_url
LMNR_FORCE_HTTP: *lmnr_force_http
LMNR_HTTP_PORT: *lmnr_http_port
LMNR_PROJECT_API_KEY: *lmnr_project_api_key
command:
- /usr/local/bin/openhands-agent-server
- "--port"
- "60000"
run_as_user: 10001
run_as_group: 10001
fs_group: 10001
keycloak:
image:
repository: '{{repl LocalRegistryNamespace }}/keycloak'
keycloakConfigCli:
image:
repository: '{{repl LocalRegistryNamespace }}/keycloak-config-cli'
waitForDb:
image: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/postgresql:latest'
postgresql:
image:
repository: '{{repl LocalRegistryNamespace }}/postgresql'
redis:
image:
repository: '{{repl LocalRegistryNamespace }}/redis'
minio:
image:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/minio'
litellm-helm:
image:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/litellm-database'
runtime-api:
image:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/runtime-api'
replicated:
image:
registry: '{{repl LocalRegistryHost }}'
repository: '{{repl LocalRegistryNamespace }}/replicated-sdk-image'
crdCheck:
image:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/kubectl'
# Compute NO_PROXY variable, then evaluate proxy_enabled gate.
# Assignments produce no output; the when value resolves to just the boolean.
- when: >-
{{repl $derive := ConfigOptionEquals "hostname_mode" "derive" }}
{{repl $bd := ConfigOption "base_domain" }}
{{repl $appDomain := $derive | ternary (printf "app.%s" $bd) (ConfigOption "app_hostname") }}
{{repl $authDomain := $derive | ternary (printf "auth.app.%s" $bd) (ConfigOption "auth_hostname") }}
{{repl $llmDomain := $derive | ternary (printf "llm-proxy.%s" $bd) (ConfigOption "llm_proxy_hostname") }}
{{repl $runtimeApiDomain := $derive | ternary (printf "runtime-api.%s" $bd) (ConfigOption "runtime_api_hostname") }}
{{repl $runtimeBaseDomain := $derive | ternary (printf "runtime.%s" $bd) (ConfigOption "runtime_base_hostname") }}
{{repl $computedNoProxy := "127.0.0.1,cluster.local,keycloak,keycloak-headless,kubernetes,localhost,oh-main-lite-llm,oh-main-runtime-api,openhands-integrations-service,openhands-litellm,openhands-mcp-service,openhands-minio,openhands-runtime-api,openhands-service,svc" }}
{{repl if ne $bd "" }}{{repl $computedNoProxy = printf "%s,%s" $computedNoProxy $bd }}{{repl end }}
{{repl if ne $appDomain "" }}{{repl $computedNoProxy = printf "%s,%s" $computedNoProxy $appDomain }}{{repl end }}
{{repl if ne $authDomain "" }}{{repl $computedNoProxy = printf "%s,%s" $computedNoProxy $authDomain }}{{repl end }}
{{repl if ne $llmDomain "" }}{{repl $computedNoProxy = printf "%s,%s" $computedNoProxy $llmDomain }}{{repl end }}
{{repl if ne $runtimeApiDomain "" }}{{repl $computedNoProxy = printf "%s,%s" $computedNoProxy $runtimeApiDomain }}{{repl end }}
{{repl if ne $runtimeBaseDomain "" }}{{repl $computedNoProxy = printf "%s,%s" $computedNoProxy $runtimeBaseDomain }}{{repl end }}
{{repl if ConfigOptionEquals "analytics_enabled" "1" }}{{repl $computedNoProxy = printf "%s,laminar-app-server-service,laminar-clickhouse,laminar-frontend,laminar-postgres,laminar-query-engine,laminar-quickwit-control-plane,laminar-quickwit-indexer,laminar-quickwit-janitor,laminar-quickwit-metastore,laminar-quickwit-searcher,laminar-rabbitmq,laminar-redis" $computedNoProxy }}{{repl end }}
{{repl if ne (ConfigOption "no_proxy") "" }}{{repl $computedNoProxy = printf "%s,%s" $computedNoProxy (ConfigOption "no_proxy") }}{{repl end }}
{{repl ConfigOptionEquals "proxy_enabled" "1" }}
recursiveMerge: true
values:
env:
HTTP_PROXY: '{{repl ConfigOption "http_proxy"}}'
HTTPS_PROXY: '{{repl ConfigOption "https_proxy"}}'
NO_PROXY: '{{repl $computedNoProxy}}'
SSL_VERIFY: '{{repl if ConfigOptionEquals "ssl_verify" "1"}}True{{repl else}}False{{repl end}}'
OH_AGENT_SERVER_ENV: '{{repl printf "{\"HTTP_PROXY\": \"%s\", \"HTTPS_PROXY\": \"%s\", \"NO_PROXY\": \"%s\", \"SSL_VERIFY\": \"%s\", \"GIT_SSL_NO_VERIFY\": \"%s\", \"SSL_CERT_FILE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"REQUESTS_CA_BUNDLE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"GIT_SSL_CAINFO\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"CURL_CA_BUNDLE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"NODE_EXTRA_CA_CERTS\": \"/etc/openhands/ca-bundle/ca-bundle.crt\"}" (ConfigOption "http_proxy") (ConfigOption "https_proxy") $computedNoProxy (ConfigOptionEquals "ssl_verify" "1" | ternary "True" "False") (ConfigOptionEquals "ssl_verify" "1" | ternary "False" "True") }}'
keycloak:
extraEnvVars:
# Existing KC env vars (must be repeated because recursiveMerge replaces arrays)
- name: KC_FEATURES
value: token-exchange,admin-fine-grained-authz
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_PROXY_HEADERS
value: "xforwarded"
- name: KC_SPI_LOGIN_PROTOCOL_OPENID_CONNECT_LEGACY_LOGOUT_REDIRECT_URI
value: "true"
- name: KC_TRUSTSTORE_PATHS
value: /etc/openhands/ca-bundle/ca-bundle.crt
- name: KC_DB_URL_PROPERTIES
value: 'sslmode={{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_ssl_mode"}}{{repl else}}prefer{{repl end}}'
# Proxy env vars
- name: HTTP_PROXY
value: '{{repl ConfigOption "http_proxy"}}'
- name: HTTPS_PROXY
value: '{{repl ConfigOption "https_proxy"}}'
- name: NO_PROXY
value: '{{repl $computedNoProxy}}'
litellm-helm:
envVars:
HTTP_PROXY: '{{repl ConfigOption "http_proxy"}}'
HTTPS_PROXY: '{{repl ConfigOption "https_proxy"}}'
NO_PROXY: '{{repl $computedNoProxy}}'
SSL_VERIFY: '{{repl if ConfigOptionEquals "ssl_verify" "1"}}True{{repl else}}False{{repl end}}'
- when: '{{repl and (ConfigOptionEquals "llm_provider" "azure") (ConfigOptionEquals "azure_auth_method" "api_key") }}'
recursiveMerge: true
values:
env:
# azure-<first line of "Azure OpenAI Deployments">. Upgrades seed the
# textarea's first line from the legacy azure_deployment_name value,
# so this stays byte-identical to today's azure-<deployment> default.
LITELLM_DEFAULT_MODEL: 'litellm_proxy/azure-repl{{ $first := "" }}repl{{ range $raw := splitList "\n" (ConfigOption "azure_deployments" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not $first) }}repl{{ $first = $m }}repl{{ end }}repl{{ end }}repl{{ $first }}'
OH_AGENT_SERVER_ENV: '{{repl printf "{\"AZURE_API_KEY\": \"%s\", \"AZURE_API_BASE\": \"%s\", \"AZURE_API_VERSION\": \"%s\"}" (ConfigOption "azure_api_key") (ConfigOption "azure_endpoint") (ConfigOption "azure_api_version") }}'
litellm-helm:
proxy_config:
# One azure-<name> -> azure/<name> entry per non-empty line of the
# "Azure OpenAI Deployments" KOTS textarea (trimmed, blanks
# skipped). The route names follow the same azure-<deployment>
# scheme as the old single-deployment field, and upgrades seed the
# first line from the legacy value, so existing user/org settings
# keep resolving without aliases (an admin who later removes that
# line orphans settings still referencing it — accepted, the app
# shows an unavailable badge).
model_list: repl{{ $models := list }}repl{{ $seen := dict }}repl{{ range $raw := splitList "\n" (ConfigOption "azure_deployments" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if $m }}repl{{ $name := printf "azure-%s" $m }}repl{{ if not (hasKey $seen $name) }}repl{{ $seen = set $seen $name true }}repl{{ $models = append $models (dict "model_name" $name "litellm_params" (dict "model" (printf "azure/%s" $m) "api_key" "os.environ/AZURE_API_KEY" "api_base" "os.environ/AZURE_API_BASE" "api_version" "os.environ/AZURE_API_VERSION")) }}repl{{ end }}repl{{ end }}repl{{ end }}repl{{ $models | mustToJson }}
- when: '{{repl and (ConfigOptionEquals "llm_provider" "azure") (ConfigOptionEquals "azure_auth_method" "service_principal") }}'
recursiveMerge: true
values:
env:
# Same first-line rule as the api_key block above.
LITELLM_DEFAULT_MODEL: 'litellm_proxy/azure-repl{{ $first := "" }}repl{{ range $raw := splitList "\n" (ConfigOption "azure_deployments" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not $first) }}repl{{ $first = $m }}repl{{ end }}repl{{ end }}repl{{ $first }}'
litellm-helm:
proxy_config:
# Same structure as the api_key block above: one azure-<name>
# entry per line of "Azure OpenAI Deployments", all using
# service-principal litellm_params.
model_list: repl{{ $models := list }}repl{{ $seen := dict }}repl{{ range $raw := splitList "\n" (ConfigOption "azure_deployments" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if $m }}repl{{ $name := printf "azure-%s" $m }}repl{{ if not (hasKey $seen $name) }}repl{{ $seen = set $seen $name true }}repl{{ $models = append $models (dict "model_name" $name "litellm_params" (dict "model" (printf "azure/%s" $m) "api_base" "os.environ/AZURE_API_BASE" "api_version" "os.environ/AZURE_API_VERSION" "tenant_id" "os.environ/AZURE_TENANT_ID" "client_id" "os.environ/AZURE_CLIENT_ID" "client_secret" "os.environ/AZURE_CLIENT_SECRET")) }}repl{{ end }}repl{{ end }}repl{{ end }}repl{{ $models | mustToJson }}
# Custom / Local LLM — pass the configured model through as a full LiteLLM
# model string. OpenAI-compatible custom endpoints should be configured as
# `openai/<model>`; non-OpenAI-shaped gateways should use their provider
# prefix, for example `anthropic/claude-opus-4-7`.
- when: '{{repl ConfigOptionEquals "llm_provider" "custom" }}'
recursiveMerge: true
values:
env:
# litellm_proxy/<route name of the first line of "Custom LLM
# Models">, where the route name is the segment after the last
# slash. For upgraded installs the env value changes from the old
# fixed litellm_proxy/custom-llm, but it resolves to the same
# backend model (the first line is seeded from the legacy
# custom_model value), and the old custom-llm route name is still
# served via the hidden alias in the model_list below, so existing
# user/org settings keep working.
LITELLM_DEFAULT_MODEL: 'litellm_proxy/repl{{ $first := "" }}repl{{ range $raw := splitList "\n" (ConfigOption "custom_models" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not $first) }}repl{{ $first = splitList "/" $m | last }}repl{{ end }}repl{{ end }}repl{{ $first }}'
OH_AGENT_SERVER_ENV: '{{repl printf "{\"CUSTOM_API_KEY\": \"%s\", \"CUSTOM_API_BASE\": \"%s\", \"SSL_CERT_FILE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"REQUESTS_CA_BUNDLE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"GIT_SSL_CAINFO\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"CURL_CA_BUNDLE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"NODE_EXTRA_CA_CERTS\": \"/etc/openhands/ca-bundle/ca-bundle.crt\"}" (ConfigOption "custom_api_key") (ConfigOption "custom_base_url") }}'
litellm-helm:
proxy_config:
# One entry per non-empty line of the "Custom LLM Models" KOTS
# textarea: a line containing a slash is used as the full LiteLLM
# model string verbatim, a bare name becomes openai/<name>; the
# route name is the part after the last slash. All entries share
# the same api_key / api_base / extra_headers params. A hidden
# alias entry named custom-llm — the route name the old
# single-model config served — is appended pointing at the SAME
# litellm_params as the first line, tagged with model_info
# openhands_hidden: true (the app-side discovery filters that tag
# out of the model dropdown) and openhands_canonical: <first
# line's route name> (lets the app display the alias under its
# canonical name), so user/org settings from before the textarea
# existed keep resolving. The alias is skipped only if a line
# already produces a route literally named custom-llm.
model_list: repl{{ $headers := dict }}repl{{ if ConfigOptionEquals "custom_llm_extra_headers_enabled" "1" }}repl{{ $headers = ConfigOption "custom_llm_extra_headers" | trim | default "{}" | mustFromJson }}repl{{ end }}repl{{ $models := list }}repl{{ $seen := dict }}repl{{ range $raw := splitList "\n" (ConfigOption "custom_models" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if $m }}repl{{ $model := $m }}repl{{ if not (contains "/" $m) }}repl{{ $model = printf "openai/%s" $m }}repl{{ end }}repl{{ $name := splitList "/" $m | last }}repl{{ if not (hasKey $seen $name) }}repl{{ $seen = set $seen $name true }}repl{{ $models = append $models (dict "model_name" $name "litellm_params" (dict "model" $model "api_key" "os.environ/CUSTOM_API_KEY" "api_base" "os.environ/CUSTOM_API_BASE" "extra_headers" $headers)) }}repl{{ end }}repl{{ end }}repl{{ end }}repl{{ if and $models (not (hasKey $seen "custom-llm")) }}repl{{ $models = append $models (dict "model_name" "custom-llm" "litellm_params" (index (index $models 0) "litellm_params") "model_info" (dict "openhands_hidden" true "openhands_canonical" (index (index $models 0) "model_name"))) }}repl{{ end }}repl{{ $models | mustToJson }}
- when: '{{repl ConfigOptionEquals "google_api_type" "vertex" }}'
recursiveMerge: true
values:
env:
# First non-empty line of the "Vertex AI Models" KOTS textarea,
# falling back to gemini-2.5-flash — today's hardcoded default and
# the textarea's prefilled line — if it resolves empty.
LITELLM_DEFAULT_MODEL: 'litellm_proxy/repl{{ $first := "" }}repl{{ range $raw := splitList "\n" (ConfigOption "vertex_models" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not $first) }}repl{{ $first = $m }}repl{{ end }}repl{{ end }}repl{{ $first | default "gemini-2.5-flash" }}'
OH_AGENT_SERVER_ENV: '{{repl printf "{\"VERTEXAI_CREDENTIALS\": %s, \"VERTEXAI_PROJECT\": \"%s\", \"VERTEXAI_LOCATION\": \"%s\", \"SSL_CERT_FILE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"REQUESTS_CA_BUNDLE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"GIT_SSL_CAINFO\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"CURL_CA_BUNDLE\": \"/etc/openhands/ca-bundle/ca-bundle.crt\", \"NODE_EXTRA_CA_CERTS\": \"/etc/openhands/ca-bundle/ca-bundle.crt\"}" (ConfigOptionData "google_vertex_credentials" | toJson) (ConfigOption "google_vertex_project_id") (ConfigOption "google_vertex_location") }}'
litellm-helm:
# volumes / volumeMounts are arrays — recursiveMerge replaces them,
# so the ca-bundle entry from spec.values.litellm-helm above must
# be repeated here alongside the google-credentials entry.
volumes:
- name: openhands-ca-bundle
configMap:
name: openhands-ca-bundle
- name: google-application-credentials
secret:
secretName: litellm-env-secrets
items:
- key: "GOOGLE_APPLICATION_CREDENTIALS_JSON"
path: "vertex_credentials.json"
volumeMounts:
- name: openhands-ca-bundle
mountPath: /etc/openhands/ca-bundle
readOnly: true
- name: google-application-credentials
mountPath: /etc/gcloud/vertex_credentials.json
subPath: vertex_credentials.json
readOnly: true
envVars:
GOOGLE_APPLICATION_CREDENTIALS: "/etc/gcloud/vertex_credentials.json"
proxy_config:
# One <name> -> vertex_ai/<name> entry per non-empty line of the
# "Vertex AI Models" KOTS textarea. The route names follow the
# same scheme as the old hardcoded gemini-2.5-flash route, and the
# textarea is prefilled with that exact line, so existing user/org
# settings keep resolving without aliases (an admin who later
# removes it orphans settings still referencing it — accepted, the
# app shows an unavailable badge). If the textarea resolves empty,
# fall back to the old hardcoded route.
model_list: repl{{ $models := list }}repl{{ $seen := dict }}repl{{ range $raw := splitList "\n" (ConfigOption "vertex_models" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not (hasKey $seen $m)) }}repl{{ $seen = set $seen $m true }}repl{{ $models = append $models (dict "model_name" $m "litellm_params" (dict "model" (printf "vertex_ai/%s" $m) "vertex_project" "os.environ/VERTEXAI_PROJECT" "vertex_location" "os.environ/VERTEXAI_LOCATION")) }}repl{{ end }}repl{{ end }}repl{{ if not $models }}repl{{ $models = list (dict "model_name" "gemini-2.5-flash" "litellm_params" (dict "model" "vertex_ai/gemini-2.5-flash" "vertex_project" "os.environ/VERTEXAI_PROJECT" "vertex_location" "os.environ/VERTEXAI_LOCATION")) }}repl{{ end }}repl{{ $models | mustToJson }}
vertexAI:
enabled: true
project: '{{repl ConfigOption "google_vertex_project_id"}}'
location: '{{repl ConfigOption "google_vertex_location"}}'
# AWS Bedrock — static access keys. LiteLLM speaks Bedrock natively via the
# AWS SDK; the AWS_* env vars are sourced from litellm-env-secrets and
# referenced in model_list via os.environ/* so the keys never appear in
# rendered ConfigMaps.
- when: '{{repl and (ConfigOptionEquals "llm_provider" "bedrock") (ConfigOptionEquals "aws_auth_method" "static_keys") }}'
recursiveMerge: true
values:
env:
# litellm_proxy/<first line of "Bedrock Model IDs">. For upgraded
# installs the env value changes from the old fixed
# litellm_proxy/bedrock, but it resolves to the same backend model
# (the first line is seeded from the legacy bedrock_model_id value),
# and the old bedrock route name is still served via the hidden
# alias in the model_list below, so existing user/org settings keep
# working.
LITELLM_DEFAULT_MODEL: 'litellm_proxy/repl{{ $first := "" }}repl{{ range $raw := splitList "\n" (ConfigOption "bedrock_model_ids" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not $first) }}repl{{ $first = $m }}repl{{ end }}repl{{ end }}repl{{ $first }}'
OH_AGENT_SERVER_ENV: '{{repl printf "{\"AWS_ACCESS_KEY_ID\": \"%s\", \"AWS_SECRET_ACCESS_KEY\": \"%s\", \"AWS_REGION_NAME\": \"%s\"}" (ConfigOption "aws_access_key_id") (ConfigOption "aws_secret_access_key") (ConfigOption "aws_region_name") }}'
litellm-helm:
proxy_config:
# One <id> -> bedrock/<id> entry per non-empty line of the
# "Bedrock Model IDs" KOTS textarea, with static-key
# litellm_params. A hidden alias entry named bedrock — the route
# name the old single-model config served — is appended pointing
# at the SAME litellm_params as the first line, tagged with
# model_info openhands_hidden: true (the app-side discovery
# filters that tag out of the model dropdown) and
# openhands_canonical: <first line's route name> (lets the app
# display the alias under its canonical name), so user/org
# settings from before the textarea existed keep resolving. The
# alias is skipped only if a line is literally named bedrock.
model_list: repl{{ $models := list }}repl{{ $seen := dict }}repl{{ range $raw := splitList "\n" (ConfigOption "bedrock_model_ids" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not (hasKey $seen $m)) }}repl{{ $seen = set $seen $m true }}repl{{ $models = append $models (dict "model_name" $m "litellm_params" (dict "model" (printf "bedrock/%s" $m) "aws_access_key_id" "os.environ/AWS_ACCESS_KEY_ID" "aws_secret_access_key" "os.environ/AWS_SECRET_ACCESS_KEY" "aws_region_name" "os.environ/AWS_REGION_NAME")) }}repl{{ end }}repl{{ end }}repl{{ if and $models (not (hasKey $seen "bedrock")) }}repl{{ $models = append $models (dict "model_name" "bedrock" "litellm_params" (index (index $models 0) "litellm_params") "model_info" (dict "openhands_hidden" true "openhands_canonical" (index (index $models 0) "model_name"))) }}repl{{ end }}repl{{ $models | mustToJson }}
# AWS Bedrock — instance profile / IRSA. Omit AWS_ACCESS_KEY_ID/SECRET so
# boto3 falls back to its default credential chain (IMDS on EC2, web
# identity token on EKS). Region still injected explicitly.
- when: '{{repl and (ConfigOptionEquals "llm_provider" "bedrock") (ConfigOptionEquals "aws_auth_method" "instance_profile") }}'
recursiveMerge: true
values:
env:
# Same first-line rule and hidden bedrock alias as the static-keys
# block above.
LITELLM_DEFAULT_MODEL: 'litellm_proxy/repl{{ $first := "" }}repl{{ range $raw := splitList "\n" (ConfigOption "bedrock_model_ids" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not $first) }}repl{{ $first = $m }}repl{{ end }}repl{{ end }}repl{{ $first }}'
litellm-helm:
proxy_config:
# Same structure as the static-keys block above: one entry per
# line of "Bedrock Model IDs" plus the hidden bedrock alias,
# region-only litellm_params (credentials come from the instance
# profile).
model_list: repl{{ $models := list }}repl{{ $seen := dict }}repl{{ range $raw := splitList "\n" (ConfigOption "bedrock_model_ids" | replace "\r" "\n") }}repl{{ $m := trim $raw }}repl{{ if and $m (not (hasKey $seen $m)) }}repl{{ $seen = set $seen $m true }}repl{{ $models = append $models (dict "model_name" $m "litellm_params" (dict "model" (printf "bedrock/%s" $m) "aws_region_name" "os.environ/AWS_REGION_NAME")) }}repl{{ end }}repl{{ end }}repl{{ if and $models (not (hasKey $seen "bedrock")) }}repl{{ $models = append $models (dict "model_name" "bedrock" "litellm_params" (index (index $models 0) "litellm_params") "model_info" (dict "openhands_hidden" true "openhands_canonical" (index (index $models 0) "model_name"))) }}repl{{ end }}repl{{ $models | mustToJson }}
# Path-based sandbox routing: route every runtime at <base>/{id} through a
# shared Gateway instead of per-sandbox Ingresses at {id}.<base>. Traefik's
# kubernetesGateway provider (enabled in embedded-cluster.yaml) installs
# the Gateway API CRDs and serves the Gateway.
- when: '{{repl ConfigOptionEquals "runtime_routing_mode" "path" }}'
recursiveMerge: true
values:
env:
RUNTIME_ROUTING_MODE: "path"
RUNTIME_URL_PATTERN: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}runtime.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "runtime_base_hostname"}}{{repl end}}/{runtime_id}'
runtime-api:
env:
RUNTIME_ROUTING_MODE: "path"
RUNTIME_URL_SEPARATOR: "/"
USE_GATEWAY_API: "true"
GATEWAY_NAME: "sandbox-gateway"
RUNTIME_CERT_SECRET: "openhands-tls"
ingressBase:
enabled: false
sandboxGateway:
enabled: true
hostname: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}runtime.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "runtime_base_hostname"}}{{repl end}}'
tlsSecretName: openhands-tls
- when: '{{repl ConfigOptionEquals "analytics_enabled" "1" }}'
recursiveMerge: true
values:
env:
LMNR_BASE_URL: *lmnr_base_url
LMNR_FORCE_HTTP: *lmnr_force_http
LMNR_HTTP_PORT: *lmnr_http_port
LMNR_PROJECT_API_KEY: *lmnr_project_api_key
laminar:
enabled: true
httpPort: 8000
appServer:
loadBalancer:
enabled: false
global:
cloudProvider: "aws"
clickhouse:
persistence:
storageClass: "openebs-hostpath" # default storage class for the k0s embededed cluster
s3:
enabled: false
frontend:
extraEnv:
- name: AUTH_KEYCLOAK_ID
valueFrom:
secretKeyRef:
name: keycloak-realm
key: client-id
- name: AUTH_KEYCLOAK_SECRET
valueFrom:
secretKeyRef:
name: keycloak-realm
key: client-secret
- name: AUTH_KEYCLOAK_ISSUER
value: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}https://auth.app.{{repl ConfigOption "base_domain"}}/realms/allhands{{repl else}}https://{{repl ConfigOption "auth_hostname"}}/realms/allhands{{repl end}}'
ingress:
enabled: true
hostname: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}analytics.app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "analytics_hostname"}}{{repl end}}'
className: "traefik"
tls:
enabled: true
secretName: "openhands-tls"
clusterIssuer: "" # leave empty — cert-manager not needed
env:
nextauthUrl: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}https://analytics.app.{{repl ConfigOption "base_domain"}}{{repl else}}https://{{repl ConfigOption "analytics_hostname"}}{{repl end}}'
nextPublicUrl: '{{repl if ConfigOptionEquals "hostname_mode" "derive"}}https://analytics.app.{{repl ConfigOption "base_domain"}}{{repl else}}https://{{repl ConfigOption "analytics_hostname"}}{{repl end}}'
postgres:
persistence:
storageClass: "openebs-hostpath"
rabbitmq:
persistence:
storageClass: "openebs-hostpath"
- when: '{{repl ConfigOptionEquals "plugin_directory_enabled" "1" }}'
recursiveMerge: true
values:
plugin-directory:
enabled: true
appUrl: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "app_hostname"}}{{repl end}}/plugins'
# OpenHands API host (same host as appUrl, without the /plugins path).
# The curl template appends /api/v1/app-conversations to this base.
curlApiUrl: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "app_hostname"}}{{repl end}}'
appEnv:
MARKETPLACE_SOURCE: '{{repl ConfigOption "plugin_directory_marketplace_source"}}'
database:
host: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_host"}}{{repl else}}oh-main-postgresql{{repl end}}'
port: '{{repl if ConfigOptionEquals "postgres_type" "external_postgres"}}{{repl ConfigOption "external_postgres_port"}}{{repl else}}5432{{repl end}}'
oidc:
issuerUrl: 'https://{{repl if ConfigOptionEquals "hostname_mode" "derive"}}auth.app.{{repl ConfigOption "base_domain"}}{{repl else}}{{repl ConfigOption "auth_hostname"}}{{repl end}}'
client:
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/plugin-directory-client'
server:
image:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/plugin-directory-server'
imagePullSecrets:
- name: '{{repl ImagePullSecretName }}'
builder:
keycloak:
enabled: true
litellm-helm:
enabled: true
minio:
enabled: true
postgresql:
enabled: true
redis:
enabled: true
runtime-api:
enabled: true