From b853fb709a4d9ec1c5cde0c39cdff00fbbb22369 Mon Sep 17 00:00:00 2001 From: jiahui Date: Fri, 24 Apr 2026 14:57:10 +0800 Subject: [PATCH] init deploy helm chart --- frontend/providers/devbox/deploy/Kubefile | 10 +- .../deploy/charts/devbox-frontend/Chart.yaml | 6 + .../devbox-frontend-values.yaml | 18 ++ .../devbox-frontend/templates/_helpers.tpl | 62 ++++++ .../charts/devbox-frontend/templates/app.yaml | 20 ++ .../devbox-frontend/templates/configmap.yaml | 10 + .../devbox-frontend/templates/deployment.yaml | 147 ++++++++++++++ .../devbox-frontend/templates/ingress.yaml | 64 ++++++ .../devbox-frontend/templates/service.yaml | 16 ++ .../deploy/charts/devbox-frontend/values.yaml | 102 ++++++++++ .../deploy/devbox-frontend-entrypoint.sh | 128 ++++++++++++ .../devbox/deploy/manifests/appcr.yaml.tmpl | 18 -- .../devbox/deploy/manifests/deploy.yaml.tmpl | 189 ------------------ .../devbox/deploy/manifests/ingress.yaml.tmpl | 51 ----- 14 files changed, 576 insertions(+), 265 deletions(-) create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/Chart.yaml create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/devbox-frontend-values.yaml create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/templates/_helpers.tpl create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/templates/app.yaml create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/templates/configmap.yaml create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/templates/deployment.yaml create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/templates/ingress.yaml create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/templates/service.yaml create mode 100644 frontend/providers/devbox/deploy/charts/devbox-frontend/values.yaml create mode 100644 frontend/providers/devbox/deploy/devbox-frontend-entrypoint.sh delete mode 100644 frontend/providers/devbox/deploy/manifests/appcr.yaml.tmpl delete mode 100644 frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl delete mode 100644 frontend/providers/devbox/deploy/manifests/ingress.yaml.tmpl diff --git a/frontend/providers/devbox/deploy/Kubefile b/frontend/providers/devbox/deploy/Kubefile index b5729882e692..d03496743cb2 100644 --- a/frontend/providers/devbox/deploy/Kubefile +++ b/frontend/providers/devbox/deploy/Kubefile @@ -3,11 +3,7 @@ FROM scratch USER 65532:65532 COPY registry registry -COPY manifests manifests +COPY charts charts +COPY devbox-frontend-entrypoint.sh devbox-frontend-entrypoint.sh -ENV cloudDomain="127.0.0.1.nip.io" -ENV cloudPort="" -ENV certSecretName="wildcard-cert" -ENV registryAddr="sealos.hub:5000" - -CMD ["kubectl apply -f manifests"] +CMD ["bash devbox-frontend-entrypoint.sh"] diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/Chart.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/Chart.yaml new file mode 100644 index 000000000000..669358de19d1 --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: devbox-frontend +description: Sealos Devbox Frontend Helm Chart +type: application +version: 1.0.0 +appVersion: "latest" diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/devbox-frontend-values.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/devbox-frontend-values.yaml new file mode 100644 index 000000000000..be1766a60fae --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/devbox-frontend-values.yaml @@ -0,0 +1,18 @@ +# User-customizable values for devbox frontend. + +devboxConfig: + tlsRejectUnauthorized: "1" + documentUrlZH: https://sealos.run/docs/overview/intro + documentUrlEN: https://sealos.io/docs/overview/intro + privacyUrlZH: https://sealos.run/docs/msa/privacy-policy + privacyUrlEN: https://sealos.io/docs/msa/privacy-policy + monitorURL: http://launchpad-monitor.sealos.svc.cluster.local:8428 + accountURL: http://account-service.account-system.svc.cluster.local:2333 + rootRuntimeNamespace: devbox-system + currencySymbol: usd + gpuEnable: "false" + retagSvcURL: http://devbox-service.devbox-system.svc.cluster.local:8092 + devboxAffinityEnable: "true" + squashEnable: "false" + enableImportFeature: "false" + enableWebideFeature: "false" diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/_helpers.tpl b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/_helpers.tpl new file mode 100644 index 000000000000..0090e2ba615e --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "devbox-frontend.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "devbox-frontend.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "devbox-frontend.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "devbox-frontend.labels" -}} +helm.sh/chart: {{ include "devbox-frontend.chart" . }} +{{ include "devbox-frontend.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "devbox-frontend.selectorLabels" -}} +app.kubernetes.io/name: {{ include "devbox-frontend.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +External hostname without scheme. +*/}} +{{- define "devbox-frontend.externalHost" -}} +{{- $host := .Values.ingress.host | default (printf "devbox.%s" .Values.devboxConfig.cloudDomain) -}} +{{- $port := toString .Values.devboxConfig.cloudPort -}} +{{- if and $port (ne $port "") -}} +{{- printf "%s:%s" $host $port -}} +{{- else -}} +{{- $host -}} +{{- end -}} +{{- end }} diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/app.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/app.yaml new file mode 100644 index 000000000000..d25973ccd255 --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/app.yaml @@ -0,0 +1,20 @@ +{{- if .Values.app.enabled }} +apiVersion: app.sealos.io/v1 +kind: App +metadata: + name: {{ .Values.app.name }} + namespace: {{ .Values.app.namespace }} +spec: + data: + desc: Devbox + url: {{ printf "https://%s" (include "devbox-frontend.externalHost" .) | quote }} + icon: {{ printf "https://%s/logo.svg" (include "devbox-frontend.externalHost" .) | quote }} + i18n: + zh: + name: Devbox + zh-Hans: + name: Devbox + name: {{ .Values.app.name }} + type: {{ .Values.app.type }} + displayType: {{ .Values.app.displayType }} +{{- end }} diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/configmap.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/configmap.yaml new file mode 100644 index 000000000000..39c92e73f584 --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "devbox-frontend.fullname" . }}-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "devbox-frontend.labels" . | nindent 4 }} +data: + config.yaml: |- + addr: :3000 diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/deployment.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/deployment.yaml new file mode 100644 index 000000000000..7bfdb911e720 --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/deployment.yaml @@ -0,0 +1,147 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "devbox-frontend.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "devbox-frontend.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "devbox-frontend.selectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 25% + maxSurge: 25% + template: + metadata: + labels: + {{- include "devbox-frontend.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: devbox-frontend-init + image: "{{ .Values.image }}" + imagePullPolicy: {{ .Values.imagePullPolicy }} + env: + - name: DATABASE_URL + value: {{ .Values.devboxConfig.databaseURL | quote }} + - name: DATABASE_PROVIDER + value: {{ .Values.global.featureConfigs.database.type | quote }} + command: + - sh + - -c + args: + - |- + cd /app/providers/devbox + DB_PROVIDER="${DATABASE_PROVIDER:-cockroachdb}" + SCHEMA_PATH="./prisma/cockroach/schema.prisma" + if [ "$DB_PROVIDER" = "postgresql" ] || [ "$DB_PROVIDER" = "postgres" ] || [ "$DB_PROVIDER" = "pg" ]; then + SCHEMA_PATH="./prisma/postgresql/schema.prisma" + HAS_UUID_FN=$(psql "$DATABASE_URL" -tAc "SELECT 1 FROM pg_proc WHERE proname='gen_random_uuid' LIMIT 1;") + if [ "$HAS_UUID_FN" != "1" ]; then + psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c 'CREATE OR REPLACE FUNCTION public.gen_random_uuid() RETURNS uuid LANGUAGE SQL VOLATILE AS $func$ SELECT md5(random()::text || clock_timestamp()::text || txid_current()::text)::uuid; $func$;' + fi + MIGRATION="20260125094114_add_template_repository_kind" + HAS_MIGRATION=$(psql "$DATABASE_URL" -tAc "SELECT 1 FROM _prisma_migrations WHERE migration_name='${MIGRATION}' AND rolled_back_at IS NULL LIMIT 1;") + if [ "$HAS_MIGRATION" = "1" ]; then + prisma migrate deploy --schema "$SCHEMA_PATH" + exit 0 + fi + HAS_ENUM=$(psql "$DATABASE_URL" -tAc "SELECT 1 FROM pg_enum e JOIN pg_type t ON t.oid = e.enumtypid WHERE t.typname IN ('TemplateRepositoryKind','template_repository_kind') AND e.enumlabel = 'SERVICE' LIMIT 1;") + if [ "$HAS_ENUM" = "1" ]; then + prisma migrate resolve --schema "$SCHEMA_PATH" --applied "$MIGRATION" + fi + fi + prisma migrate deploy --schema "$SCHEMA_PATH" + containers: + - name: {{ include "devbox-frontend.name" . }} + image: "{{ .Values.image }}" + imagePullPolicy: {{ .Values.imagePullPolicy }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + env: + - name: NODE_TLS_REJECT_UNAUTHORIZED + value: {{ .Values.devboxConfig.tlsRejectUnauthorized | quote }} + - name: DOCUMENT_URL_ZH + value: {{ .Values.devboxConfig.documentUrlZH | quote }} + - name: DOCUMENT_URL_EN + value: {{ .Values.devboxConfig.documentUrlEN | quote }} + - name: PRIVACY_URL_ZH + value: {{ .Values.devboxConfig.privacyUrlZH | quote }} + - name: PRIVACY_URL_EN + value: {{ .Values.devboxConfig.privacyUrlEN | quote }} + - name: SEALOS_DOMAIN + value: {{ .Values.devboxConfig.cloudDomain | quote }} + - name: INGRESS_SECRET + value: {{ .Values.devboxConfig.certSecretName | quote }} + - name: REGISTRY_ADDR + value: {{ .Values.devboxConfig.registryAddr | quote }} + - name: DEVBOX_AFFINITY_ENABLE + value: {{ .Values.devboxConfig.devboxAffinityEnable | quote }} + - name: MONITOR_URL + value: {{ .Values.devboxConfig.monitorURL | quote }} + - name: SQUASH_ENABLE + value: {{ .Values.devboxConfig.squashEnable | quote }} + - name: ACCOUNT_URL + value: {{ .Values.devboxConfig.accountURL | quote }} + - name: ROOT_RUNTIME_NAMESPACE + value: {{ .Values.devboxConfig.rootRuntimeNamespace | quote }} + - name: INGRESS_DOMAIN + value: {{ default .Values.devboxConfig.cloudDomain .Values.devboxConfig.ingressDomain | quote }} + - name: CURRENCY_SYMBOL + value: {{ .Values.devboxConfig.currencySymbol | quote }} + - name: GPU_ENABLE + value: {{ .Values.devboxConfig.gpuEnable | quote }} + - name: PRIVACY_URL + value: {{ .Values.devboxConfig.privacyUrlZH | quote }} + - name: RETAG_SVC_URL + value: {{ .Values.devboxConfig.retagSvcURL | quote }} + - name: JWT_SECRET + value: {{ .Values.devboxConfig.jwtInternal | quote }} + - name: REGION_UID + value: {{ .Values.devboxConfig.regionUID | quote }} + - name: DATABASE_URL + value: {{ .Values.devboxConfig.databaseURL | quote }} + - name: DATABASE_PROVIDER + value: {{ .Values.global.featureConfigs.database.type | quote }} + - name: ENABLE_IMPORT_FEATURE + value: {{ .Values.devboxConfig.enableImportFeature | quote }} + - name: ENABLE_WEBIDE_FEATURE + value: {{ .Values.devboxConfig.enableWebideFeature | quote }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: devbox-frontend-volume + mountPath: /config.yaml + subPath: config.yaml + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: devbox-frontend-volume + configMap: + name: {{ include "devbox-frontend.fullname" . }}-config diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/ingress.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/ingress.yaml new file mode 100644 index 000000000000..5dfec875c11d --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/ingress.yaml @@ -0,0 +1,64 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "devbox-frontend.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "devbox-frontend.labels" . | nindent 4 }} + annotations: + kubernetes.io/ingress.class: {{ .Values.ingress.className | quote }} + nginx.ingress.kubernetes.io/configuration-snippet: | + more_clear_headers "X-Frame-Options:"; + more_set_headers "Content-Security-Policy: default-src * blob: data: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }}; img-src * data: blob: resource: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} resource: *.baidu.com *.bdstatic.com; frame-src 'self' {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} mailto: tel: weixin: mtt: *.baidu.com; frame-ancestors 'self' https://{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} https://*.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }}"; + more_set_headers "X-Xss-Protection: 1; mode=block"; + higress.io/response-header-control-remove: X-Frame-Options + higress.io/response-header-control-update: | + Content-Security-Policy "default-src * blob: data: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }}; img-src * data: blob: resource: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} resource: *.baidu.com *.bdstatic.com; frame-src 'self' {{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} mailto: tel: weixin: mtt: *.baidu.com; frame-ancestors 'self' https://{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }} https://*.{{ .Values.devboxConfig.cloudDomain }}{{- if .Values.devboxConfig.cloudPort -}}:{{ .Values.devboxConfig.cloudPort }}{{- end }}" + X-Xss-Protection "1; mode=block" + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.ingress.className | quote }} + rules: + - host: {{ .Values.ingress.host | default (printf "devbox.%s" .Values.devboxConfig.cloudDomain) | quote }} + http: + paths: + - pathType: Prefix + path: / + backend: + service: + name: {{ include "devbox-frontend.fullname" . }} + port: + number: {{ .Values.service.port }} + tls: + - hosts: + - {{ .Values.ingress.host | default (printf "devbox.%s" .Values.devboxConfig.cloudDomain) | quote }} + secretName: {{ .Values.ingress.certSecretName | quote }} +--- +{{- if .Values.ingress.challenge.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: devbox-challenge + namespace: {{ .Release.Namespace }} + labels: + {{- include "devbox-frontend.labels" . | nindent 4 }} + annotations: + kubernetes.io/ingress.class: {{ .Values.ingress.className | quote }} +spec: + ingressClassName: {{ .Values.ingress.className | quote }} + rules: + - host: {{ .Values.ingress.challenge.host | quote }} + http: + paths: + - pathType: Prefix + path: {{ .Values.ingress.challenge.path | quote }} + backend: + service: + name: {{ include "devbox-frontend.fullname" . }} + port: + number: {{ .Values.service.port }} +{{- end }} +{{- end }} diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/service.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/service.yaml new file mode 100644 index 000000000000..f1824b46f1f2 --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "devbox-frontend.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "devbox-frontend.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - name: http + port: {{ .Values.service.port }} + protocol: TCP + targetPort: {{ .Values.service.port }} + selector: + {{- include "devbox-frontend.selectorLabels" . | nindent 4 }} diff --git a/frontend/providers/devbox/deploy/charts/devbox-frontend/values.yaml b/frontend/providers/devbox/deploy/charts/devbox-frontend/values.yaml new file mode 100644 index 000000000000..6a2f9af543f9 --- /dev/null +++ b/frontend/providers/devbox/deploy/charts/devbox-frontend/values.yaml @@ -0,0 +1,102 @@ +# Default values for devbox frontend helm chart. + +replicaCount: 1 + +global: + featureConfigs: + database: + # Database backend for Prisma migrations. + # Supported values: cockroachdb, postgresql + type: cockroachdb +image: ghcr.io/labring/sealos-devbox-frontend:latest +imagePullPolicy: Always + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "devbox-frontend" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: + runAsNonRoot: true + runAsUser: 1001 + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + +service: + type: ClusterIP + port: 3000 + +ingress: + enabled: true + className: nginx + host: "" + certSecretName: wildcard-cert + annotations: {} + challenge: + enabled: true + host: "" + path: /api/.well-known/devbox-domain-challenge + +app: + enabled: true + namespace: app-system + name: devbox + displayType: normal + type: iframe + +# Auto-configured values are injected by devbox-frontend-entrypoint.sh from sealos-config. +devboxConfig: + cloudDomain: 127.0.0.1.nip.io + cloudPort: "" + certSecretName: wildcard-cert + registryAddr: sealos.hub:5000 + tlsRejectUnauthorized: "1" + documentUrlZH: https://sealos.run/docs/overview/intro + documentUrlEN: https://sealos.io/docs/overview/intro + privacyUrlZH: https://sealos.run/docs/msa/privacy-policy + privacyUrlEN: https://sealos.io/docs/msa/privacy-policy + monitorURL: http://launchpad-monitor.sealos.svc.cluster.local:8428 + accountURL: http://account-service.account-system.svc.cluster.local:2333 + rootRuntimeNamespace: devbox-system + ingressDomain: "" + currencySymbol: usd + gpuEnable: "false" + retagSvcURL: http://devbox-service.devbox-system.svc.cluster.local:8092 + jwtInternal: "" + regionUID: "" + databaseURL: "" + devboxAffinityEnable: "true" + squashEnable: "false" + enableImportFeature: "false" + enableWebideFeature: "false" + +resources: + limits: + cpu: 2000m + memory: 2048Mi + requests: + cpu: 100m + memory: 128Mi + +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: "app.kubernetes.io/name" + operator: In + values: + - devbox-frontend + topologyKey: "kubernetes.io/hostname" + +nodeSelector: {} + +tolerations: [] diff --git a/frontend/providers/devbox/deploy/devbox-frontend-entrypoint.sh b/frontend/providers/devbox/deploy/devbox-frontend-entrypoint.sh new file mode 100644 index 000000000000..c64c55f3c46d --- /dev/null +++ b/frontend/providers/devbox/deploy/devbox-frontend-entrypoint.sh @@ -0,0 +1,128 @@ +#!/bin/bash +set -e + +# Default values +RELEASE_NAME=${RELEASE_NAME:-"devbox-frontend"} +RELEASE_NAMESPACE=${RELEASE_NAMESPACE:-"devbox-frontend"} +CHART_PATH=${CHART_PATH:-"./charts/devbox-frontend"} +AUTO_CONFIG_ENABLED=${AUTO_CONFIG_ENABLED:-"true"} + +# HELM_OPTS and HELM_OPTIONS support +HELM_OPTS=${HELM_OPTS:-""} +HELM_OPTIONS=${HELM_OPTIONS:-""} + +HELM_ARGS="" +[ -n "${HELM_OPTIONS}" ] && HELM_ARGS="${HELM_ARGS} ${HELM_OPTIONS}" +[ -n "${HELM_OPTS}" ] && HELM_ARGS="${HELM_ARGS} ${HELM_OPTS}" + +get_cm_value() { + local namespace="$1" + local name="$2" + local key="$3" + kubectl get configmap "${name}" -n "${namespace}" -o "jsonpath={.data.${key}}" 2>/dev/null || true +} + +adopt_namespaced_resource() { + local namespace="$1" + local kind="$2" + local name="$3" + if kubectl -n "${namespace}" get "${kind}" "${name}" >/dev/null 2>&1; then + echo "Adopting ${kind} ${namespace}/${name}..." + kubectl -n "${namespace}" label "${kind}" "${name}" app.kubernetes.io/managed-by=Helm --overwrite >/dev/null 2>&1 || true + kubectl -n "${namespace}" annotate "${kind}" "${name}" meta.helm.sh/release-name="${RELEASE_NAME}" meta.helm.sh/release-namespace="${RELEASE_NAMESPACE}" --overwrite >/dev/null 2>&1 || true + fi +} + +check_deployment_selector() { + local deployment_name="$1" + local current_selector + + current_selector=$(kubectl -n "${RELEASE_NAMESPACE}" get deployment "${deployment_name}" -o jsonpath='{.spec.selector.matchLabels}' 2>/dev/null || echo "{}") + if echo "${current_selector}" | grep -q "app.kubernetes.io/name"; then + return 0 + fi + + return 1 +} + +recreate_deployment_if_needed() { + local deployment_name="$1" + + if ! kubectl -n "${RELEASE_NAMESPACE}" get deployment "${deployment_name}" >/dev/null 2>&1; then + return 0 + fi + + if check_deployment_selector "${deployment_name}"; then + echo "Deployment ${deployment_name} selector is compatible, skipping recreation..." + return 0 + fi + + echo "Deployment ${deployment_name} selector incompatible, recreating..." + local backup_file="/tmp/${deployment_name}-backup-$(date +%s).yaml" + kubectl -n "${RELEASE_NAMESPACE}" get deployment "${deployment_name}" -o yaml > "${backup_file}" + kubectl -n "${RELEASE_NAMESPACE}" delete deployment "${deployment_name}" --ignore-not-found=true + echo "Deployment ${deployment_name} recreated (backup saved to ${backup_file})" +} + +if [ "${AUTO_CONFIG_ENABLED}" = "true" ]; then + echo "Auto-configuring from sealos-system/sealos-config..." + + CLOUD_DOMAIN=${CLOUD_DOMAIN:-"$(get_cm_value sealos-system sealos-config cloudDomain)"} + CLOUD_PORT=${CLOUD_PORT:-"$(get_cm_value sealos-system sealos-config cloudPort)"} + CERT_SECRET_NAME=${CERT_SECRET_NAME:-"wildcard-cert"} + REGISTRY_ADDR=${REGISTRY_ADDR:-"sealos.hub:5000"} + JWT_INTERNAL=${JWT_INTERNAL:-"$(get_cm_value sealos-system sealos-config jwtInternal)"} + REGION_UID=${REGION_UID:-"$(get_cm_value sealos-system sealos-config regionUID)"} + DATABASE_URL=${DATABASE_URL:-"$(get_cm_value sealos-system sealos-config databaseGlobalCockroachdbURI)"} + DATABASE_URL=${DATABASE_URL//global/devboxdb} + + [ -n "${CLOUD_DOMAIN}" ] && HELM_ARGS="${HELM_ARGS} --set-string devboxConfig.cloudDomain=${CLOUD_DOMAIN} --set-string ingress.host=devbox.${CLOUD_DOMAIN}" + [ -n "${CLOUD_PORT}" ] && HELM_ARGS="${HELM_ARGS} --set-string devboxConfig.cloudPort=${CLOUD_PORT}" + [ -n "${CERT_SECRET_NAME}" ] && HELM_ARGS="${HELM_ARGS} --set-string devboxConfig.certSecretName=${CERT_SECRET_NAME} --set-string ingress.certSecretName=${CERT_SECRET_NAME}" + [ -n "${REGISTRY_ADDR}" ] && HELM_ARGS="${HELM_ARGS} --set-string devboxConfig.registryAddr=${REGISTRY_ADDR}" + [ -n "${JWT_INTERNAL}" ] && HELM_ARGS="${HELM_ARGS} --set-string devboxConfig.jwtInternal=${JWT_INTERNAL}" + [ -n "${REGION_UID}" ] && HELM_ARGS="${HELM_ARGS} --set-string devboxConfig.regionUID=${REGION_UID}" + [ -n "${DATABASE_URL}" ] && HELM_ARGS="${HELM_ARGS} --set-string devboxConfig.databaseURL=${DATABASE_URL}" +fi + +echo "Checking and adopting existing resources..." +if kubectl get namespace "${RELEASE_NAMESPACE}" >/dev/null 2>&1; then + kubectl label namespace "${RELEASE_NAMESPACE}" app.kubernetes.io/managed-by=Helm --overwrite >/dev/null 2>&1 || true + kubectl annotate namespace "${RELEASE_NAMESPACE}" meta.helm.sh/release-name="${RELEASE_NAME}" meta.helm.sh/release-namespace="${RELEASE_NAMESPACE}" --overwrite >/dev/null 2>&1 || true + + adopt_namespaced_resource "${RELEASE_NAMESPACE}" configmap devbox-frontend-config + adopt_namespaced_resource "${RELEASE_NAMESPACE}" service devbox-frontend + adopt_namespaced_resource "${RELEASE_NAMESPACE}" ingress devbox-frontend + adopt_namespaced_resource "${RELEASE_NAMESPACE}" ingress devbox-challenge +fi + +echo "Checking Deployment selector compatibility..." +recreate_deployment_if_needed "devbox-frontend" + +if kubectl -n "${RELEASE_NAMESPACE}" get deployment devbox-frontend >/dev/null 2>&1; then + adopt_namespaced_resource "${RELEASE_NAMESPACE}" deployment devbox-frontend +fi + +if kubectl get namespace app-system >/dev/null 2>&1; then + adopt_namespaced_resource app-system app devbox +fi + +SERVICE_NAME="devbox-frontend" +USER_VALUES_PATH="/root/.sealos/cloud/values/apps/${SERVICE_NAME}-values.yaml" + +if [ ! -f "${USER_VALUES_PATH}" ]; then + mkdir -p "$(dirname "${USER_VALUES_PATH}")" + cp "./charts/${SERVICE_NAME}/${SERVICE_NAME}-values.yaml" "${USER_VALUES_PATH}" +fi + +GLOBALS_FILE="/root/.sealos/cloud/values/global.yaml" +if [ -f "${GLOBALS_FILE}" ]; then + echo "Merging global values from ${GLOBALS_FILE} into user values..." + HELM_ARGS="$HELM_ARGS -f ${GLOBALS_FILE}" +fi + +echo "Deploying Helm chart..." +helm upgrade -i "${RELEASE_NAME}" -n "${RELEASE_NAMESPACE}" --create-namespace "${CHART_PATH}" \ + -f "./charts/${SERVICE_NAME}/values.yaml" \ + -f "${USER_VALUES_PATH}" \ + ${HELM_ARGS} diff --git a/frontend/providers/devbox/deploy/manifests/appcr.yaml.tmpl b/frontend/providers/devbox/deploy/manifests/appcr.yaml.tmpl deleted file mode 100644 index dceb28fc69e6..000000000000 --- a/frontend/providers/devbox/deploy/manifests/appcr.yaml.tmpl +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: app.sealos.io/v1 -kind: App -metadata: - name: devbox - namespace: app-system -spec: - data: - desc: DevBox - url: 'https://devbox.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}' - icon: 'https://devbox.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}/logo.svg' - i18n: - zh: - name: DevBox - zh-Hans: - name: DevBox - name: DevBox - type: iframe - displayType: normal diff --git a/frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl b/frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl deleted file mode 100644 index 1035208e75c8..000000000000 --- a/frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl +++ /dev/null @@ -1,189 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - app: devbox-frontend - name: devbox-frontend ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: devbox-frontend-config - namespace: devbox-frontend -data: - config.yaml: |- - addr: :3000 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: devbox-frontend - namespace: devbox-frontend -spec: - replicas: 1 - selector: - matchLabels: - app: devbox-frontend - strategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 25% - maxSurge: 25% - template: - metadata: - labels: - app: devbox-frontend - spec: - initContainers: - - name: devbox-frontend-init - image: ghcr.io/labring/sealos-devbox-frontend:latest - imagePullPolicy: Always - env: - - name: DATABASE_URL - value: {{ .databaseUrl }} # -nsealos cm desktop-frontend-config ->database->global + devbox - - name: DATABASE_PROVIDER - value: "{{ default "cockroachdb" .databaseProvider }}" - command: - - sh - - -c - args: - - |- - cd /app/providers/devbox - DB_PROVIDER="${DATABASE_PROVIDER:-cockroachdb}" - SCHEMA_PATH="./prisma/cockroach/schema.prisma" - if [ "$DB_PROVIDER" = "postgresql" ] || [ "$DB_PROVIDER" = "postgres" ] || [ "$DB_PROVIDER" = "pg" ]; then - SCHEMA_PATH="./prisma/postgresql/schema.prisma" - HAS_UUID_FN=$(psql "$DATABASE_URL" -tAc "SELECT 1 FROM pg_proc WHERE proname='gen_random_uuid' LIMIT 1;") - if [ "$HAS_UUID_FN" != "1" ]; then - psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c 'CREATE OR REPLACE FUNCTION public.gen_random_uuid() RETURNS uuid LANGUAGE SQL VOLATILE AS $func$ SELECT md5(random()::text || clock_timestamp()::text || txid_current()::text)::uuid; $func$;' - fi - MIGRATION="20260125094114_add_template_repository_kind" - HAS_MIGRATION=$(psql "$DATABASE_URL" -tAc "SELECT 1 FROM _prisma_migrations WHERE migration_name='${MIGRATION}' AND rolled_back_at IS NULL LIMIT 1;") - if [ "$HAS_MIGRATION" = "1" ]; then - prisma migrate deploy --schema "$SCHEMA_PATH" - exit 0 - fi - HAS_ENUM=$(psql "$DATABASE_URL" -tAc "SELECT 1 FROM pg_enum e JOIN pg_type t ON t.oid = e.enumtypid WHERE t.typname IN ('TemplateRepositoryKind','template_repository_kind') AND e.enumlabel = 'SERVICE' LIMIT 1;") - if [ "$HAS_ENUM" = "1" ]; then - prisma migrate resolve --schema "$SCHEMA_PATH" --applied "$MIGRATION" - fi - fi - prisma migrate deploy --schema "$SCHEMA_PATH" - containers: - - name: devbox-frontend - env: - - name: NODE_TLS_REJECT_UNAUTHORIZED - value: "{{ default "1" .tlsRejectUnauthorized }}" - - name: DOCUMENT_URL_ZH - value: https://sealos.run/docs/overview/intro - - name: DOCUMENT_URL_EN - value: https://sealos.io/docs/overview/intro - - name: PRIVACY_URL_ZH - value: https://sealos.run/docs/msa/privacy-policy - - name: PRIVACY_URL_EN - value: https://sealos.io/docs/msa/privacy-policy - - name: SEALOS_DOMAIN - value: {{ .cloudDomain }} - - name: SSH_DOMAIN - value: {{ default "" .sshDomain }} - - name: INGRESS_SECRET - value: wildcard-cert - - name: REGISTRY_ADDR - value: {{ .registryAddr }} - - name: DEVBOX_AFFINITY_ENABLE - value: 'true' - - name: MONITOR_URL - value: http://launchpad-monitor.sealos.svc.cluster.local:8428 - - name: ACCOUNT_URL - value: http://account-service.account-system.svc.cluster.local:2333 - - name: ROOT_RUNTIME_NAMESPACE - value: devbox-system - - name: INGRESS_DOMAIN - value: {{ .cloudDomain }} - - name: CURRENCY_SYMBOL - value: usd # 'shellCoin' | 'cny' | 'usd' - - name: GPU_ENABLE - value: 'false' - - name: STORAGE_LIMIT - value: 10Gi # default is 10Gi - - name: DEVBOX_RUNTIME_CLASS_NAME - value: devbox-runtime - - name: RETAG_SVC_URL - value: http://devbox-service.devbox-system.svc.cluster.local:8092 - - name: JWT_SECRET - value: {{ .jwtSecret }} # -nsealos cm desktop-frontend-config ->jwt->Internal - - name: REGION_UID - value: {{ .regionUid }} # -nsealos cm desktop-frontend-config ->regionUid - - name: DATABASE_URL - value: {{ .databaseUrl }} # -nsealos cm desktop-frontend-config ->database->global + devbox - - name: DATABASE_PROVIDER - value: "{{ default "cockroachdb" .databaseProvider }}" - - name: ENABLE_IMPORT_FEATURE - value: 'false' - - name: ENABLE_WEBIDE_FEATURE - value: 'false' - - name: ENABLE_ADVANCED_CONFIG - value: 'false' - - name: CPU_SLIDE_MARK_LIST - value: '1,2,4,8,16' - - name: MEMORY_SLIDE_MARK_LIST - value: '2,4,8,16,32' - - name: DEVBOX_DOMAIN_CHALLENGE_SECRET - value: {{ .devboxDomainChallengeSecret }} - - name: NFS_STORAGE_CLASS_NAME - value: {{ default "nfs-csi" .nfsStorageClassName }} - - name: CUSTOM_SCRIPTS - value: '{{ default "[]" .customScripts }}' - securityContext: - runAsNonRoot: true - runAsUser: 1001 - allowPrivilegeEscalation: false - capabilities: - drop: - - 'ALL' - resources: - limits: - cpu: 2000m - memory: 2048Mi - requests: - cpu: 100m - memory: 128Mi - # do not modify this image, it is used for CI/CD - image: ghcr.io/labring/sealos-devbox-frontend:latest - imagePullPolicy: Always - volumeMounts: - - name: devbox-frontend-volume - mountPath: /config.yaml - subPath: config.yaml - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchExpressions: - - key: 'app' - operator: In - values: - - devbox-frontend - topologyKey: 'kubernetes.io/hostname' - volumes: - - name: devbox-frontend-volume - configMap: - name: devbox-frontend-config ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: devbox-frontend - name: devbox-frontend - namespace: devbox-frontend -spec: - ports: - - name: http - port: 3000 - protocol: TCP - targetPort: 3000 - selector: - app: devbox-frontend diff --git a/frontend/providers/devbox/deploy/manifests/ingress.yaml.tmpl b/frontend/providers/devbox/deploy/manifests/ingress.yaml.tmpl deleted file mode 100644 index b05a597fed18..000000000000 --- a/frontend/providers/devbox/deploy/manifests/ingress.yaml.tmpl +++ /dev/null @@ -1,51 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: nginx - nginx.ingress.kubernetes.io/configuration-snippet: | - more_clear_headers "X-Frame-Options:"; - more_set_headers "Content-Security-Policy: default-src * blob: data: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; img-src * data: blob: resource: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource: *.baidu.com *.bdstatic.com; frame-src 'self' {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} mailto: tel: weixin: mtt: *.baidu.com; frame-ancestors 'self' https://{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} https://*.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}"; - more_set_headers "X-Xss-Protection: 1; mode=block"; - higress.io/response-header-control-remove: X-Frame-Options - higress.io/response-header-control-update: | - Content-Security-Policy "default-src * blob: data: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; img-src * data: blob: resource: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} resource: *.baidu.com *.bdstatic.com; frame-src 'self' {{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} mailto: tel: weixin: mtt: *.baidu.com; frame-ancestors 'self' https://{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }} https://*.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}" - X-Xss-Protection "1; mode=block" - name: devbox-frontend - namespace: devbox-frontend -spec: - rules: - - host: devbox.{{ .cloudDomain }} - http: - paths: - - pathType: Prefix - path: / - backend: - service: - name: devbox-frontend - port: - number: 3000 - tls: - - hosts: - - devbox.{{ .cloudDomain }} - secretName: {{ .certSecretName }} # wildcard-cert ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: nginx - name: devbox-challenge - namespace: devbox-frontend -spec: - rules: - - host: "" - http: - paths: - - backend: - service: - name: devbox-frontend - port: - number: 3000 - path: /api/.well-known/devbox-domain-challenge - pathType: Prefix