diff --git a/.woodpecker/publish-charts.yml b/.woodpecker/publish-charts.yml
deleted file mode 100644
index c4610c9a..00000000
--- a/.woodpecker/publish-charts.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-labels:
-  type: exec
-  platform: linux/amd64
-
-steps:
-- name: publish-charts
-  image: bash
-  commands:
-  - export VERSION=$(sed 's#v##' <<< $CI_COMMIT_TAG)
-  - export IMAGE_TAG=$([ ! -z $CI_COMMIT_TAG ] && echo \"$VERSION\" || echo \"canary\")
-  - export APP_VERSION=$([ -z $CI_COMMIT_TAG ] && echo $CI_COMMIT_SHA || echo $VERSION)
-  - export CHART_VERSION=$([ -z $CI_COMMIT_TAG ] && echo 1.0.0 || echo $VERSION)
-  - sed -i s#{{repository}}#oci://$DRYCC_REGISTRY/$([ -z $CI_COMMIT_TAG ] && echo charts-testing || echo charts)#g charts/workflow/Chart.yaml
-  - helm dependency update charts/workflow
-  - helm package -u charts/$${CI_REPO_NAME} --version $CHART_VERSION --app-version $APP_VERSION
-  - echo $CONTAINER_PASSWORD | helm registry login $DRYCC_REGISTRY -u $CONTAINER_USERNAME --password-stdin
-  - helm push $${CI_REPO_NAME}-$CHART_VERSION.tgz oci://$DRYCC_REGISTRY/$([ -z $CI_COMMIT_TAG ] && echo charts-testing || echo charts)
-  - bash _scripts/generate_cache.sh 
-  secrets:
-  - drycc_registry
-  - container_username
-  - container_password
-  when:
-    event:
-    - tag
-    - push
diff --git a/.woodpecker/publish.yml b/.woodpecker/publish.yml
new file mode 100644
index 00000000..f3e76f2a
--- /dev/null
+++ b/.woodpecker/publish.yml
@@ -0,0 +1,54 @@
+labels:
+  type: exec
+  platform: linux/amd64
+
+steps:
+- name: publish-charts
+  image: bash
+  commands:
+  - export VERSION=$(sed 's#v##' <<< $CI_COMMIT_TAG)
+  - export IMAGE_TAG=$([ ! -z $CI_COMMIT_TAG ] && echo \"$VERSION\" || echo \"canary\")
+  - export APP_VERSION=$([ -z $CI_COMMIT_TAG ] && echo $CI_COMMIT_SHA || echo $VERSION)
+  - export CHART_VERSION=$([ -z $CI_COMMIT_TAG ] && echo 1.0.0 || echo $VERSION)
+  - sed -i s#{{repository}}#oci://$DRYCC_REGISTRY/$([ -z $CI_COMMIT_TAG ] && echo charts-testing || echo charts)#g charts/$${CI_REPO_NAME}/Chart.yaml
+  - helm dependency update charts/$${CI_REPO_NAME}
+  - helm package -u charts/$${CI_REPO_NAME} --version $CHART_VERSION --app-version $APP_VERSION
+  - echo $CONTAINER_PASSWORD | helm registry login $DRYCC_REGISTRY -u $CONTAINER_USERNAME --password-stdin
+  - helm push $${CI_REPO_NAME}-$CHART_VERSION.tgz oci://$DRYCC_REGISTRY/$([ -z $CI_COMMIT_TAG ] && echo charts-testing || echo charts)
+  - bash _scripts/generate_cache.sh 
+  environment:
+    DRYCC_REGISTRY:
+      from_secret: drycc_registry
+    CONTAINER_USERNAME:
+      from_secret: container_username
+    CONTAINER_PASSWORD:
+      from_secret: container_password
+  when:
+    event:
+    - tag
+    - push
+    - cron
+
+- name: publish-scripts
+  image: bash
+  commands:
+  - podman run --rm
+      -e CI_FORGE_TYPE=github
+      -e CI_PIPELINE_EVENT=tag
+      -e CI_REPO_OWNER="$CI_REPO_OWNER"
+      -e CI_REPO_NAME="$CI_REPO_NAME"
+      -e CI_COMMIT_REF="refs/tags/$CI_COMMIT_TAG"
+      -e PLUGIN_API_KEY="$GITHUB_TOKEN"
+      -e PLUGIN_BASE_URL="https://api.github.com/"
+      -e PLUGIN_UPLOAD_URL="https://uploads.github.com/"
+      -e PLUGIN_OVERWRITE="true"
+      -e PLUGIN_FILES="*.sh"
+      -v $(pwd):$(pwd)
+      -w $(pwd)
+      docker.io/woodpeckerci/plugin-release
+  environment:
+    GITHUB_TOKEN:
+      from_secret: github_token
+  when:
+    event:
+    - tag
diff --git a/README.md b/README.md
index 94ec8ba7..af2b2ba4 100644
--- a/README.md
+++ b/README.md
@@ -17,12 +17,10 @@ Please see below for links and descriptions of each component:
 - [builder](https://github.com/drycc/builder) - Git server and source-to-image component
 - [imagebuilder](https://github.com/drycc/imagebuilder) - The builder for Container Image and [CNCF Buildpacks](https://buildpacks.io/) based applications
 - [fluentbit](https://github.com/drycc/fluentbit) - Backend log shipping mechanism for `drycc logs`
-- [postgres](https://github.com/drycc/postgres) - The central database
+- [database](https://github.com/drycc/database) - Central database based on PostgreSQL
 - [registry](https://github.com/drycc/registry) - The Container registry
-- [logger](https://github.com/drycc/logger) - The (in-memory) log buffer for `drycc logs`
-- [monitor](https://github.com/drycc/monitor) - The platform monitoring components
-- [prometheus](https://github.com/drycc/prometheus) - The monitor database
-- [rabbitmq](https://github.com/drycc/rabbitmq) - RabbitMQ is a message broker used with controller celery
+- [grafana](https://github.com/drycc/grafana) - Central monitor grafana
+- [victoriametrics](https://github.com/drycc/victoriametrics) - The monitor database
 - [storage](https://github.com/drycc/storage) - The in-cluster, kubernetes storage, s3 api compatible, hybrid storage system.
 - [workflow-cli](https://github.com/drycc/workflow-cli) - Workflow CLI `drycc`
 
diff --git a/charts/workflow/Chart.yaml b/charts/workflow/Chart.yaml
index 559ee331..41910932 100755
--- a/charts/workflow/Chart.yaml
+++ b/charts/workflow/Chart.yaml
@@ -10,45 +10,43 @@ dependencies:
 - name: controller
   repository: {{repository}}
   version: x.x.x
-- name: database
-  repository: {{repository}}
-  version: x.x.x
-- name: timeseries
-  repository: {{repository}}
-  version: x.x.x
 - name: fluentbit
   repository: {{repository}}
   version: x.x.x
 - name: gateway
   repository: {{repository}}
   version: x.x.x
-- name: redis
+- name: quickwit
   repository: {{repository}}
   version: x.x.x
-- name: logger
+- name: valkey
   repository: {{repository}}
   version: x.x.x
+  condition: valkey.enabled
 - name: storage
   repository: {{repository}}
   version: x.x.x
-- name: monitor
+  condition: storage.enabled
+- name: grafana
   repository: {{repository}}
   version: x.x.x
-- name: registry
-  repository: {{repository}}
-  version: x.x.x
-- name: registry-proxy
+  condition: grafana.enabled
+- name: database
   repository: {{repository}}
   version: x.x.x
-- name: rabbitmq
+  condition: database.enabled
+- name: registry
   repository: {{repository}}
   version: x.x.x
+  condition: registry.enabled
 - name: passport
   repository: {{repository}}
   version: x.x.x
-- name: prometheus
+  condition: passport.enabled
+- name: victoriametrics
   repository: {{repository}}
   version: x.x.x
+  condition: victoriametrics.enabled
 description: Drycc Workflow
 home: https://github.com/drycc/workflow
 maintainers:
diff --git a/charts/workflow/values.yaml b/charts/workflow/values.yaml
index 59fc3e00..3bd11487 100644
--- a/charts/workflow/values.yaml
+++ b/charts/workflow/values.yaml
@@ -1,159 +1,19 @@
 # This is the global configuration file for Workflow
 
 # If the service is used by a component alone, it needs to be configured inside the component.
-# Refer to rabbitmqUrl and databaseUrl of the controller component. 
 # If the service is dependent on multiple components and there is state synchronization,
 # it needs to be configured separately, such as storage.
 
 global:
   # Admin email, used for each component to send email to administrator
   email: "drycc@drycc.cc"
-
-  # Set the location of Workflow's Object Storage
-  #
-  # Valid values are:
-  # - on-cluster: Run drycc storage within the Kubernetes cluster
-  # - off-cluster: Run Storage outside the Kubernetes cluster (Compatible with s3 API)
-  storageLocation: "on-cluster"
-
-  # Set the location of Workflow's PostgreSQL database
-  #
-  # Valid values are:
-  # - on-cluster: Run PostgreSQL within the Kubernetes cluster (credentials are generated
-  #   automatically; backups are sent to object storage
-  #   configured above)
-  # - off-cluster: Run PostgreSQL outside the Kubernetes cluster
-  databaseLocation: "on-cluster"
-
-  # Set the location of Workflow's Timeseries database
-  #
-  # Valid values are:
-  # - on-cluster: Run PostgreSQL within the Kubernetes cluster (credentials are generated
-  #   automatically; backups are sent to object storage
-  #   configured above)
-  # - off-cluster: Run PostgreSQL outside the Kubernetes cluster
-  timeseriesLocation: "on-cluster"
-
-  # Set the location of Workflow's Redis instance
-  #
-  # Valid values are:
-  # - on-cluster: Run Redis within the Kubernetes cluster
-  # - off-cluster: Run Redis outside the Kubernetes cluster (configure in redis section)
-  redisLocation: "on-cluster"
-  # Set the location of Workflow's rabbitmq instance
-  # Valid values are:
-  # - on-cluster: Run Rabbitmq within the Kubernetes cluster
-  # - off-cluster: Run Rabbitmq outside the Kubernetes cluster (configure in controller section)
-  rabbitmqLocation: "on-cluster"
-  # Set the location of Workflow's prometheus cluster
-  #
-  # Valid values are:
-  # - on-cluster: Run prometheus within the Kubernetes cluster
-  # - off-cluster: prometheus is running outside of the cluster and credentials and connection information will be provided.
-  prometheusLocation: "on-cluster"
-  # Set the location of Workflow's grafana instance
-  #
-  # Valid values are:
-  # - on-cluster: Run Grafana within the Kubernetes cluster
-  # - off-cluster: Grafana is running outside of the cluster
-  grafanaLocation: "on-cluster"
-
-  # Set the location of Workflow's Registry
-  #
-  # Valid values are:
-  # - on-cluster: Run registry within the Kubernetes cluster
-  # - off-cluster: Use registry outside the Kubernetes cluster (example: drycc registry,self-hosted)
-  registryLocation: "on-cluster"
-  # Prefix for the imagepull secret created when using private registry
-  registrySecretPrefix: "private-registry"
-  # The host port to which registry proxy binds to
-  registryProxyPort: 5555
-
-  # Enable usage of RBAC authorization mode
-  #
-  # Valid values are:
-  # - true: all RBAC-related manifests will be installed (in case your cluster supports RBAC)
-  # - false: no RBAC-related manifests will be installed
-  rbac: true
-  # GatewayClass is cluster-scoped resource defined by the infrastructure provider.
-  # This resource represents a class of Gateways that can be instantiated.
-  gatewayClass: ""
-  # A domain name consists of one or more parts.
-  # Periods (.) are used to separate these parts.
-  # Each part must be 1 to 63 characters in length and can contain lowercase letters, digits, and hyphens (-).
-  # It must start and end with a lowercase letter or digit.
-  clusterDomain: "cluster.local"
   # The publicly resolvable hostname to build your cluster with.
   #
   # This will be the hostname that is used to build endpoints such as "drycc.$HOSTNAME"
   platformDomain: ""
   # Whether certManagerEnabled is true to automatically generate drycc certificates
   certManagerEnabled: false
-  # Set the location of Workflow's passport
-  #
-  # Valid values are:
-  # - on-cluster: Run passport within the Kubernetes cluster
-  # - off-cluster: Use passport outside the Kubernetes cluster
-  passportLocation: "on-cluster"
-
-storage:
-  # The following parameters are configured only when using an on-cluster Storage instance
-  csi:
-    storageClassName: drycc-storage
-    statefulset:
-      replicas: 1
-  mainnode:
-    tipd:
-      replicas: 3
-      persistence:
-        enabled: false
-        size: 5Gi
-        storageClass: ""
-    weed:
-      replicas: 3
-      persistence:
-        enabled: false
-        size: 5Gi
-        storageClass: ""
-  metanode:
-    tikv:
-      replicas: 3
-      persistence:
-        enabled: false
-        size: 5Gi
-        storageClass: ""
-    weed:
-      replicas: 3
-      persistence:
-        enabled: false
-        size: 5Gi
-        storageClass: ""
-  datanode:
-    weed:
-      replicas: 3
-      persistence:
-        enabled: false
-        size: 5Gi
-        storageClass: ""
-  # The following parameters are configured only when using an off-cluster Storage instance
-  lookup: path
-  health: ""
-  endpoint: ""
-  builderBucket: ""
-  registryBucket: ""
-  # The following parameters are general parameters
-  accesskey: ""
-  secretkey: ""
 
-# Set the default (global) way of how Application (your own) images are
-# pulled from within the Controller.
-# This can be configured per Application as well in the Controller.
-#
-# This affects pull apps and git push apps
-#
-# Values values are:
-# - Always
-# - IfNotPresent
 builder:
   service:
     # Service type default to ClusterIP, the entrance will be taken over by router.
@@ -161,6 +21,22 @@ builder:
     # If service.type is not set to NodePort, the following statement will be ignored.
     nodePort: ""
 
+# Drycc Gateway is a gateway chart that integrates cert-manager.
+gateway:
+  # GatewayClass is cluster-scoped resource defined by the infrastructure provider.
+  gatewayClass: ""
+  # acme configuration takes effect if and only if certManagerEnabled is true
+  acme:
+    server: https://acme-v02.api.letsencrypt.org/directory
+    #  EAB credentials
+    externalAccountBinding:
+      keyID: ""
+      keySecret: ""
+
+fluentbit:
+  # Interval to flush output (seconds)
+  flush: 5
+
 controller:
   appImagePullPolicy: "IfNotPresent"
   # Possible values are:
@@ -168,8 +44,10 @@ controller:
   # disabled - turns off open registration
   # admin_only - allows for registration by an admin only.
   registrationMode: "admin_only"
+  # Set app gateway, It is used for application gateway.
+  appGatewayClass: ""
   # Set storageClassName, It is used for application mount.
-  appStorageClass: "{{ .Values.storage.csi.storageClassName }}"
+  appStorageClass: "longhorn"
   # Set controller deployment  replicas
   replicas: 1
   # Set celery replicas
@@ -177,128 +55,50 @@ controller:
   ## databaseUrl and databaseReplicaUrl are will no longer use the built-in database component
   databaseUrl: ""
   databaseReplicaUrl: ""
-  # Configuring this will no longer use the built-in rabbitmq component
-  rabbitmqUrl: ""
-
-database:
-  # The following parameters are configured only when using an on-cluster Database instance
-  # The username and password to be used by the on-cluster database.
-  # If left empty they will be generated
-  # The user name should be set to lowercase letters 
-  superuser: "postgres"
-  superuserPassword: ""
-  replicator: "standby"
-  replicatorPassword: ""
-  user: ""
-  password: ""
-  persistence:
-    enabled: false # Set to true to enable persistence
-    size: 5Gi # PVC size
-    ## database data Persistent Volume Storage Class
-    ## If defined, storageClassName: <storageClass>
-    ## If set to "-", storageClassName: "", which disables dynamic provisioning
-    ## If undefined (the default) or set to null, no storageClassName spec is
-    ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
-    ##   GKE, AWS & OpenStack)
-    ##
-    ## Storage class of PV to bind. By default it looks for standard storage class.
-    ## If the PV uses a different storage class, specify that here.
-    storageClass: ""
 
-timeseries:
-  # The following parameters are configured only when using an on-cluster Database instance
-  # The username and password to be used by the on-cluster database.
-  # If left empty they will be generated
-  # The user name should be set to lowercase letters 
-  superuser: "postgres"
-  superuserPassword: ""
-  replicator: "standby"
-  replicatorPassword: ""
-  user: ""
-  password: ""
+valkey:
+  enabled: true
+  # The addrs parameters are configured only when using an off-cluster Valkey instance
+  addrs: "" # A list of clusters: "127.0.0.1:7001/1,127.0.0.2:7002/1"
+  password: "valkey password" # "" == no password
+  # The following parameters are configured only when using an on-cluster Valkey instance
+  replicas: 3
   persistence:
     enabled: false # Set to true to enable persistence
-    size: 5Gi # PVC size
-    ## database data Persistent Volume Storage Class
-    ## If defined, storageClassName: <storageClass>
-    ## If set to "-", storageClassName: "", which disables dynamic provisioning
-    ## If undefined (the default) or set to null, no storageClassName spec is
-    ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
-    ##   GKE, AWS & OpenStack)
-    ##
-    ## Storage class of PV to bind. By default it looks for standard storage class.
-    ## If the PV uses a different storage class, specify that here.
+    size: 5Gi
     storageClass: ""
 
-redis:
-  # The addrs parameters are configured only when using an off-cluster Redis instance
-  addrs: "" # A list of clusters: "127.0.0.1:7001/1,127.0.0.2:7002/1"
-  password: "redis password" # "" == no password
-  # The following parameters are configured only when using an on-cluster Redis instance
-  replicas: 1
+storage:
+  enabled: true
+  # The following parameters are general parameters
+  accesskey: ""
+  secretkey: ""
   persistence:
-    enabled: false # Set to true to enable persistence
+    enabled: false
     size: 5Gi
     storageClass: ""
 
-rabbitmq:
-  # The following parameters are configured only when using an on-cluster Rabbitmq instance
-  # If left empty they will be generated using randAlphaNum
-  username: ""
-  password: ""
-  # GCP PDs and EBS volumes are supported only
+grafana:
+  enabled: true
   persistence:
-    enabled: false # Set to true to enable persistence
-    size: 5Gi # PVC size
-    ## rabbitmq data Persistent Volume Storage Class
-    ## If defined, storageClassName: <storageClass>
-    ## If set to "-", storageClassName: "", which disables dynamic provisioning
-    ## If undefined (the default) or set to null, no storageClassName spec is
-    ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
-    ##   GKE, AWS & OpenStack)
-    ##
-    ## Storage class of PV to bind. By default it looks for standard storage class.
-    ## If the PV uses a different storage class, specify that here.
+    enabled: false
+    size: 5Gi
     storageClass: ""
 
-fluentbit:
-  # Interval to flush output (seconds)
-  flush: 5
-
-monitor:
-  grafana:
-    # Configure the following ONLY if you want persistence for on-cluster grafana
-    # GCP PDs and EBS volumes are supported only
-    persistence:
-      enabled: false # Set to true to enable persistence
-      accessMode: ReadWriteOnce
-      size: 5Gi # PVC size
-      ## grafana data Persistent Volume Storage Class
-      ## If defined, storageClassName: <storageClass>
-      ## If set to "-", storageClassName: "", which disables dynamic provisioning
-      ## If undefined (the default) or set to null, no storageClassName spec is
-      ##   set, choosing the default provisioner.  (gp2 on AWS, standard on
-      ##   GKE, AWS & OpenStack)
-      ##
-      ## Storage class of PV to bind. By default it looks for standard storage class.
-      ## If the PV uses a different storage class, specify that here.
-      storageClass: ""
-      volumeName: ""
-
 registry:
-  # The host parameters is configured only when using an off-cluster Registry instance
-  host: ""
-  # The following organization parameters are valid only in cluster-off
-  # In the case of cluster-on, the value is app name, which is dynamically assigned by builder.
-  organization: ""
+  enabled: true
   username: ""
   password: ""
   # The redirect parameters is configured only when using an on-cluster Registry instance
   # The subsection provides configuration for managing redirects from content backends.
   # For backends that support it, redirecting is disabled by default.
   redirect: "false"
+  # host port for the registry proxy in the daemonset
+  proxy:
+    port: 5555
 
 passport:
+  enabled: true
   # The following parameters are configured only when using an on-cluster passport
   # Set passport deployment replicas
   replicas: 1
@@ -306,37 +106,57 @@ passport:
   databaseUrl: ""
   databaseReplicaUrl: ""
 
-prometheus:
-  ## prometheus-server configuration##
-  prometheus-server:
-    replicas: 1
-    retention: 15d
-    # persistence config
+database:
+  enabled: true
+  # The following parameters are configured only when using an on-cluster Database instance
+  # The username and password to be used by the on-cluster database.
+  # If left empty they will be generated
+  # The user name should be set to lowercase letters 
+  superuser: "postgres"
+  superuserPassword: ""
+  replicator: "standby"
+  replicatorPassword: ""
+  user: ""
+  password: ""
+  persistence:
+    enabled: false
+    size: 5Gi
+    storageClass: ""
+
+victoriametrics:
+  enabled: true
+  ## victoriametrics vmagent configuration
+  ##
+  vmagent:
+    replicas: 3
     persistence:
-      enabled: true
-      accessMode: ReadWriteOnce
-      size: 10Gi
+      enabled: false
+      size: 5Gi
       storageClass: ""
+
+  ## victoriametrics vminsert configuration
+  ##
+  vminsert:
+    replicas: 3
+
+  ## victoriametrics vmselect configuration
+  ##
+  vmselect:
+    replicas: 3
+
+  ## victoriametrics vmstorage configuration
+  ##
+  vmstorage:
+    replicas: 3
+    persistence:
+      enabled: false
+      size: 5Gi
+      storageClass: ""
+
   ## node-exporter configuration##
-  node-exporter:
+  nodeExporter:
     enabled: true
   ## kube-state-metrics configuration
   ##
-  kube-state-metrics:
+  kubeStateMetrics:
     enabled: true
-  # Configure the following ONLY if using an off-cluster prometheus database
-  # URL configuration is only available in off-cluster prometheus database
-  url: "http://my.prometheus.url:9090"
-  # The username and password to be used by the on-cluster database.
-  username: "admin"
-  password: "admin"
-
-# Drycc Gateway is a gateway chart that integrates cert-manager.
-gateway:
-  # acme configuration takes effect if and only if certManagerEnabled is true
-  acme:
-    server: https://acme-v02.api.letsencrypt.org/directory
-    #  EAB credentials
-    externalAccountBinding:
-      keyID: ""
-      keySecret: ""
diff --git a/install-cli.sh b/install-cli.sh
new file mode 100755
index 00000000..882aa6c3
--- /dev/null
+++ b/install-cli.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+set -eo pipefail
+shopt -s expand_aliases
+
+check_platform_arch() {
+  local supported="darwin-amd64 darwin-arm64 linux-amd64 linux-386 linux-arm linux-arm64 windows-386 windows-amd64"
+
+  if ! echo "${supported}" | tr ' ' '\n' | grep -q "${PLATFORM}-${ARCH}"; then
+    cat <<EOF
+
+The Drycc Workflow CLI (drycc) is not currently supported on ${PLATFORM}-${ARCH}.
+
+See https://github.com/drycc/workflow-cli for more information.
+
+EOF
+  fi
+}
+
+PLATFORM="$(uname | tr '[:upper:]' '[:lower:]')"
+if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    DRYCC_BIN_URL_BASE="https://github.com/drycc/workflow-cli/releases"
+else
+    DRYCC_BIN_URL_BASE="https://github.com/drycc/workflow-cli/releases"
+fi
+
+# initArch discovers the architecture for this system.
+init_arch() {
+  ARCH=$(uname -m)
+  case $ARCH in
+    armv5*) ARCH="armv5";;
+    armv6*) ARCH="armv6";;
+    armv7*) ARCH="arm";;
+    aarch64) ARCH="arm64";;
+    x86) ARCH="386";;
+    x86_64) ARCH="amd64";;
+    i686) ARCH="386";;
+    i386) ARCH="386";;
+  esac
+}
+
+init_latest_version() {
+  VERSION=$(curl -Ls $DRYCC_BIN_URL_BASE|grep /drycc/workflow-cli/releases/tag/ | sed -E 's/.*\/drycc\/workflow-cli\/releases\/tag\/(v[0-9\.]+)".*/\1/g' | head -1)
+}
+
+init_arch
+init_latest_version
+check_platform_arch
+
+DRYCC_CLI="drycc-${VERSION}-${PLATFORM}-${ARCH}"
+DRYCC_CLI_PATH="${DRYCC_CLI}"
+if [ "${VERSION}" != 'stable' ]; then
+  DRYCC_CLI_PATH="${VERSION}/${DRYCC_CLI_PATH}"
+fi
+
+echo "Downloading ${DRYCC_CLI} From Google Cloud Storage..."
+echo "Downloading binary from here: ${DRYCC_BIN_URL_BASE}/download/${DRYCC_CLI_PATH}"
+curl -fsSL -o drycc "${DRYCC_BIN_URL_BASE}/download/${DRYCC_CLI_PATH}"
+
+chmod +x drycc
+
+cat <<EOF
+
+The Drycc Workflow CLI (drycc) is now available in your current directory.
+
+To learn more about Drycc Workflow, execute:
+
+    $ ./drycc --help
+
+You can also move it to other directories, such as:
+
+   $ mv $PWD/drycc /usr/local/bin
+
+EOF
diff --git a/install.sh b/install.sh
new file mode 100755
index 00000000..6c5ab750
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,846 @@
+#!/usr/bin/env bash
+set -eo pipefail
+shopt -s expand_aliases
+
+# default vars
+GATEWAY_CLASS="istio"
+CLUSTER_CIDR=${CLUSTER_CIDR:-"10.42.0.0/16"}
+SERVICE_CIDR=${SERVICE_CIDR:-"10.43.0.0/16"}
+CERT_MANAGER_ENABLED="${CERT_MANAGER_ENABLED:-false}"
+DRYCC_REGISTRY="${DRYCC_REGISTRY:-registry.drycc.cc}"
+CHARTS_URL=oci://registry.drycc.cc/$([ "$CHANNEL" == "stable" ] && echo charts || echo charts-testing)
+CONTAINERD_RUNTIMES="${CONTAINERD_RUNTIMES:-runc}"
+CONTAINERD_CONFIG_PATH="${CONTAINERD_CONFIG_PATH:-/var/lib/rancher/k3s/agent/etc/containerd}"
+mkdir -p "${CONTAINERD_CONFIG_PATH}"
+CONTAINERD_CONFIG_FILE="${CONTAINERD_CONFIG_PATH}/config.toml.tmpl"
+REGISTRY_CONFIG_PATH="${REGISTRY_CONFIG_PATH:-/etc/rancher/k3s/}"
+mkdir -p "${REGISTRY_CONFIG_PATH}"
+REGISTRY_CONFIG_FILE="${REGISTRY_CONFIG_PATH}/registries.yaml"
+
+# initArch discovers the architecture for this system.
+init_arch() {
+  ARCH=$(uname -m)
+  case $ARCH in
+    armv5*) ARCH="armv5";;
+    armv6*) ARCH="armv6";;
+    armv7*) ARCH="arm";;
+    aarch64) ARCH="arm64";;
+    x86) ARCH="386";;
+    x86_64) ARCH="amd64";;
+    i686) ARCH="386";;
+    i386) ARCH="386";;
+  esac
+}
+
+function clean_before_exit {
+    # delay before exiting, so stdout/stderr flushes through the logging system
+    rm -rf /tmp/drycc-values.yaml
+    sleep 3
+}
+trap clean_before_exit EXIT
+init_arch
+
+urlencode() {
+  # urlencode <string>
+
+  old_lang=$LANG
+  LANG=C
+
+  old_lc_collate=$LC_COLLATE
+  LC_COLLATE=C
+
+  local length="${#1}"
+  for (( i = 0; i < length; i++ )); do
+      local c="${1:i:1}"
+      case $c in
+          [a-zA-Z0-9.~_-]) printf "$c" ;;
+          *) printf '%%%02X' "'$c" ;;
+      esac
+  done
+
+  LANG=$old_lang
+  LC_COLLATE=$old_lc_collate
+}
+
+# get_latest_github_release fetches the latest GitHub release tag matching a regex pattern.
+# Usage: get_latest_github_release <org/repo> <pattern>
+#   org/repo  - GitHub organization and repository (e.g. "helm/helm")
+#   pattern   - extended regex that the tag must match (e.g. "^v[0-9]+\.[0-9]+\.[0-9]+$")
+# The function automatically selects the mirror URL based on INSTALL_DRYCC_MIRROR.
+function get_latest_github_release {
+  local repo="$1"
+  local pattern="$2"
+  local base_url
+
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]]; then
+    base_url="https://github.com/${repo}"
+  else
+    base_url="https://github.com/${repo}"
+  fi
+
+  curl -Ls "${base_url}/releases" \
+    | grep -o "href=\"/${repo}/releases/tag/[^\"]*\"" \
+    | sed "s|href=\"/${repo}/releases/tag/||; s|\"$||" \
+    | grep -E "${pattern}" \
+    | sort -Vr \
+    | head -1
+}
+
+# install_helm downloads and installs the Helm CLI tool from the latest GitHub release.
+function install_helm {
+  echo -e "\\033[32m---> Start install helm\\033[0m"
+  version=$(get_latest_github_release "helm/helm" "^v[0-9]+\.[0-9]+\.[0-9]+$")
+  tar_name="helm-${version}-linux-${ARCH}.tar.gz"
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    helm_download_url="https://github.com/helm/${tar_name}"
+  else
+    helm_download_url="https://get.helm.sh/${tar_name}"
+  fi
+  curl -fsSL -o "${tar_name}" "${helm_download_url}"
+  tar -zxvf "${tar_name}"
+  mv "linux-${ARCH}/helm" /usr/local/bin/helm
+  rm -rf "${tar_name}" "linux-${ARCH}"
+  echo -e "\\033[32m---> crun runtime install completed!\\033[0m"
+}
+
+# helm_upgrade wraps "helm upgrade --install" with retry logic and a default timeout.
+# Usage: helm_upgrade <release> <chart> [helm-options...]
+#   HELM_MAX_RETRIES    - max number of retry attempts (default: 3)
+#   HELM_RETRY_INTERVAL - seconds between retries (default: 10)
+# A default timeout of 10m0s is applied unless --timeout is explicitly passed.
+function helm_upgrade {
+  local max_retries=${HELM_MAX_RETRIES:-3}
+  local retry_interval=${HELM_RETRY_INTERVAL:-10}
+  local has_timeout=false
+  for arg in "$@"; do
+    if [[ "$arg" == "--timeout" ]]; then
+      has_timeout=true
+      break
+    fi
+  done
+  local timeout_args=()
+  if [[ "$has_timeout" == "false" ]]; then
+    timeout_args=(--timeout 10m0s)
+  fi
+  for (( attempt=1; attempt<=max_retries; attempt++ )); do
+    if helm upgrade --install "$@" "${timeout_args[@]}"; then
+      return 0
+    fi
+    if [[ $attempt -lt $max_retries ]]; then
+      echo -e "\\033[33m---> Warning: helm upgrade --install failed (attempt $attempt/$max_retries), retrying in ${retry_interval}s...\\033[0m"
+      sleep "$retry_interval"
+    fi
+  done
+  echo -e "\\033[31m---> Error: helm upgrade --install failed after $max_retries attempts\\033[0m"
+  return 1
+}
+
+# configure_os tunes kernel parameters and OS settings for Kubernetes workloads.
+# Includes: iptables cleanup, swap disable, BPF mount, sysctl overrides, CPU governor.
+function configure_os {
+  echo -e "\\033[32m---> Start configuring kernel parameters\\033[0m"
+  if [[ "$(command -v iptables)" != "" ]] ; then
+    iptables -F || echo -e "\\033[33m---> Warning: iptables -F failed, skipping (container environment?)\\033[0m"
+    iptables -X || echo -e "\\033[33m---> Warning: iptables -X failed, skipping\\033[0m"
+    iptables -F -t nat || echo -e "\\033[33m---> Warning: iptables -F -t nat failed, skipping\\033[0m"
+    iptables -X -t nat || echo -e "\\033[33m---> Warning: iptables -X -t nat failed, skipping\\033[0m"
+    iptables -P FORWARD ACCEPT || echo -e "\\033[33m---> Warning: iptables -P FORWARD ACCEPT failed, skipping\\033[0m"
+  else
+    echo -e "\\033[33m---> Warning: iptables does not exist, skipping iptables configuration...\\033[0m"
+  fi
+  swapoff -a 2>/dev/null || true
+  sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab 2>/dev/null || true
+  mount bpffs -t bpf /sys/fs/bpf 2>/dev/null || echo -e "\\033[33m---> Warning: mount bpffs failed, skipping (container environment?)\\033[0m"
+  mkdir -p /etc/sysctl.d
+  cat << EOF > /etc/sysctl.d/99-zzz-override-drycc.conf
+fs.file-max = 2097152
+fs.inotify.max_user_instances = 65535
+fs.inotify.max_user_watches = 1048576
+net.core.rmem_max = 2500000
+vm.nr_hugepages = 1024
+EOF
+  sysctl --system 2>/dev/null || echo -e "\\033[33m---> Warning: sysctl --system failed, skipping (container environment?)\\033[0m"
+
+  cpufreq=$(ls /sys/devices/system/cpu/cpu*/cpufreq >/dev/null 2>&1 || echo "false")
+  if [[ $cpufreq != "false" ]]; then
+    for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
+      echo performance > $cpu 2>/dev/null || true
+    done
+  fi
+  echo -e "\\033[32m---> Configuring kernel parameters finish\\033[0m"
+}
+
+# install_crun_runtime downloads and installs the crun OCI runtime binary.
+function install_crun_runtime {
+  echo -e "\\033[32m---> Start install crun runtime\\033[0m"
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    crun_base_url="https://github.com/containers"
+  else
+    crun_base_url="https://github.com/containers"
+  fi
+  crun_version=$(get_latest_github_release "containers/crun" "^[0-9]+\.[0-9]+(\.[0-9]+)?(-rc[0-9]+)?$")
+  crun_download_url=${crun_base_url}/crun/releases/download/${crun_version}/crun-${crun_version}-linux-${ARCH}
+  curl -sfL "${crun_download_url}" -o /usr/local/bin/crun
+  chmod a+rx /usr/local/bin/crun
+  echo -e "\\033[32m---> crun runtime install completed!\\033[0m"
+}
+
+# install_kata_runtime downloads and installs the Kata Containers runtime for VM-based isolation.
+function install_kata_runtime {
+  echo -e "\\033[32m---> Start install kata runtime\\033[0m"
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    kata_base_url="https://github.com/kata-containers"
+  else
+    kata_base_url="https://github.com/kata-containers"
+  fi
+
+  kata_version=$(get_latest_github_release "kata-containers/kata-containers" "^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$")
+  kata_package=kata-static-${kata_version}-${ARCH}.tar.zst
+  kata_download_url=${kata_base_url}/kata-containers/releases/download/${kata_version}/${kata_package}
+
+  curl -fL "${kata_download_url}" -o ${kata_package}
+  tar -I zstd -xf ${kata_package} -C /
+  ln -sf /opt/kata/bin/containerd-shim-kata-v2 /usr/local/bin/containerd-shim-kata-v2
+  ln -sf /opt/kata/bin/kata-collect-data.sh /usr/local/bin/kata-collect-data.sh
+  ln -sf /opt/kata/bin/kata-runtime /usr/local/bin/kata-runtime
+  rm -rf ${kata_package}
+  echo -e "\\033[32m---> Kata runtime install completed!\\033[0m"
+}
+
+# install_runtime configures containerd runtimes based on CONTAINERD_RUNTIMES.
+# Supported values: "runc" (default), "crun", "kata". Multiple values can be comma-separated.
+# Generates the containerd config template and installs the selected runtimes.
+function install_runtime {
+  readarray -d , -t containerd_runtimes <<<"$CONTAINERD_RUNTIMES"
+  if [[ "$CONTAINERD_RUNTIMES" =~ "crun" ]]; then
+    containerd_default_runtime="crun"
+  else
+    containerd_default_runtime="runc"
+  fi
+  cat << EOF > "${CONTAINERD_CONFIG_FILE}"
+{{ template "base" . }}
+
+[plugins.cri.containerd]
+  snapshotter = "overlayfs"
+  default_runtime_name = "${containerd_default_runtime}"
+  disable_snapshot_annotations = true
+EOF
+
+  for (( n=0; n < ${#containerd_runtimes[*]}; n++ ))
+  do
+    if [[ "${containerd_runtimes[n]}" == "kata" ]]; then
+      install_kata_runtime
+      sed -i s/sandbox_cgroup_only=false/sandbox_cgroup_only=true/g /opt/kata/share/defaults/kata-containers/configuration.toml
+      cat << EOF >> "${CONTAINERD_CONFIG_FILE}"
+[plugins.cri.containerd.runtimes.kata]
+  runtime_type = "io.containerd.kata.v2"
+  privileged_without_host_devices = true
+  pod_annotations = ["io.katacontainers.*"]
+  container_annotations = ["io.katacontainers.*"]
+[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata.options]
+  ConfigPath = "/opt/kata/share/defaults/kata-containers/configuration.toml"
+EOF
+    elif [[ "${containerd_runtimes[n]}" == "crun" ]]; then
+      install_crun_runtime
+      cat << EOF >> "${CONTAINERD_CONFIG_FILE}"
+[plugins.cri.containerd.runtimes.crun]
+  runtime_type = "io.containerd.runc.v2"
+[plugins.cri.containerd.runtimes.crun.options]
+  SystemdCgroup = true
+EOF
+    else
+      cat << EOF >> "${CONTAINERD_CONFIG_FILE}"
+[plugins.cri.containerd.runtimes.runc]
+  runtime_type = "io.containerd.runc.v2"
+[plugins.cri.containerd.runtimes.runc.options]
+  SystemdCgroup = true
+EOF
+    fi
+  done
+}
+
+# configure_registry writes container registry mirror configuration for k3s/containerd.
+# Only applies when INSTALL_DRYCC_MIRROR is set to "cn".
+function configure_registry {
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]]; then
+    cat << EOF > "${REGISTRY_CONFIG_FILE}"
+mirrors:
+  docker.io:
+    endpoint:
+    - "https://docker.m.daocloud.io"
+    - "https://docker-mirror.drycc.cc"
+    - "https://registry-1.docker.io"
+  quay.io:
+    endpoint:
+    - "https://quay.m.daocloud.io"
+    - "https://quay-mirror.drycc.cc"
+    - "https://quay.io"
+  gcr.io:
+    endpoint:
+    - "https://gcr.m.daocloud.io"
+    - "https://quay-mirror.drycc.cc"
+    - "https://gcr.io"
+  registry.k8s.io:
+    endpoint:
+    - "https://k8s.m.daocloud.io"
+    - "https://k8s-mirror.drycc.cc"
+    - "https://registry.k8s.io"
+EOF
+  fi
+}
+
+# configure_kubectl sets up kubectl defaults, enabling server-side apply by default.
+function configure_kubectl {
+  echo -e "\\033[32m---> Start configuring kubectl defaults\\033[0m"
+  mkdir -p "$HOME/.kube"
+  cat << EOF > "$HOME/.kube/kuberc"
+apiVersion: kubectl.config.k8s.io/v1beta1
+kind: Preference
+defaults:
+  # Default to server-side apply
+  - command: apply
+    options:
+      - name: server-side
+        default: "true"
+EOF
+  chmod 600 "$HOME/.kube/kuberc"
+  echo -e "\\033[32m---> Kubectl defaults configured (server-side apply enabled)\\033[0m"
+}
+
+# configure_k3s_mirrors selects the k3s install URL based on INSTALL_DRYCC_MIRROR.
+function configure_k3s_mirrors {
+  echo -e "\\033[32m---> Start configuring k3s mirrors\\033[0m"
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    INSTALL_K3S_MIRROR="${INSTALL_DRYCC_MIRROR}"
+    k3s_install_url="https://github.com/get-k3s/"
+    export INSTALL_K3S_MIRROR
+  else
+    k3s_install_url="https://get.k3s.io"
+  fi
+  echo -e "\\033[32m---> Configuring k3s mirrors finish\\033[0m"
+}
+
+# install_k3s_server installs and bootstraps a k3s server (control plane) node.
+# Configures OS, runtimes, kubectl, registry mirrors, then installs k3s with
+# embedded registry, Cilium CNI, and registers RuntimeClass resources.
+function install_k3s_server {
+  configure_os
+  install_runtime
+  configure_kubectl
+  configure_registry
+  configure_k3s_mirrors
+  INSTALL_K3S_EXEC="server ${INSTALL_K3S_EXEC} --embedded-registry --flannel-backend=none  --disable-network-policy --disable=traefik --disable=servicelb --disable-kube-proxy --cluster-cidr=${CLUSTER_CIDR} --service-cidr=${SERVICE_CIDR}"
+  if [[ -n "${K3S_DATA_DIR}" ]] ; then
+    INSTALL_K3S_EXEC="$INSTALL_K3S_EXEC --data-dir=${K3S_DATA_DIR}/rancher/k3s"
+  fi
+  if [[ -z "${K3S_URL}" ]] ; then
+    INSTALL_K3S_EXEC="$INSTALL_K3S_EXEC --cluster-init"
+  fi
+  curl -sfL "${k3s_install_url}" |INSTALL_K3S_EXEC="$INSTALL_K3S_EXEC" sh -s -
+
+  readarray -d , -t containerd_runtimes <<<"$CONTAINERD_RUNTIMES"
+  for (( n=0; n < ${#containerd_runtimes[*]}; n++ ))
+  do
+    kubectl apply -f - <<EOF
+apiVersion: node.k8s.io/v1
+kind: RuntimeClass
+metadata:
+  name: ${containerd_runtimes[n]}
+handler: ${containerd_runtimes[n]}
+EOF
+  done
+}
+
+# install_k3s_agent installs and joins a k3s agent (worker) node to an existing cluster.
+function install_k3s_agent {
+  configure_os
+  install_runtime
+  configure_kubectl
+  configure_registry
+  configure_k3s_mirrors
+  if [[ -n "${K3S_DATA_DIR}" ]] ; then
+    INSTALL_K3S_EXEC="$INSTALL_K3S_EXEC --embedded-registry --data-dir=${K3S_DATA_DIR}/rancher/k3s"
+  fi
+  curl -sfL "${k3s_install_url}" |INSTALL_K3S_EXEC="$INSTALL_K3S_EXEC" sh -s -
+}
+
+# install_longhorn deploys Longhorn distributed block storage via Helm.
+# Usage: install_longhorn [helm-options...]
+#   LONGHORN_CONFIG_FILE - path to a custom Helm values file; uses defaults if unset.
+function install_longhorn {
+  options=${1:-""}
+  helm repo add longhorn https://github.com/longhorn-charts
+  helm repo update
+  if [[ -z "${LONGHORN_CONFIG_FILE}" ]] ; then
+    echo -e "\\033[32m---> Longhorn using the default configuration.\\033[0m"
+    helm_upgrade longhorn longhorn/longhorn \
+      --set csi.attacherReplicaCount=1 \
+      --set csi.provisionerReplicaCount=1 \
+      --set csi.resizerReplicaCount=1 \
+      --set csi.snapshotterReplicaCount=1 \
+      --set persistence.defaultClass=false \
+      --set longhornUI.replicas=1 \
+      --set persistence.defaultClassReplicaCount=1 \
+      --namespace longhorn-system \
+      --create-namespace $options --wait
+  else
+    helm_upgrade longhorn longhorn/longhorn -f "${LONGHORN_CONFIG_FILE}" --wait
+  fi
+  echo -e "\\033[32m---> Longhorn install completed!\\033[0m"
+}
+
+# install_mountpoint deploys the AWS Mountpoint S3 CSI driver via Helm.
+function install_mountpoint {
+  options=${1:-""}
+  helm repo add aws-mountpoint-s3-csi-driver https://github.com/mountpoint-charts
+  helm repo update
+
+  version=$(get_latest_github_release "drycc/mountpoint-s3-csi-driver" "^v[0-9]+\.[0-9]+\.[0-9]+$")
+  helm_upgrade aws-mountpoint-s3-csi-driver aws-mountpoint-s3-csi-driver/aws-mountpoint-s3-csi-driver \
+    --set supportLegacySystemDMounts=false \
+    --set image.repository=registry.drycc.cc/drycc/mountpoint-s3-csi-driver \
+    --set image.tag=${version#v} \
+    --namespace kube-system $options --wait
+  echo -e "\\033[32m---> Mountpoint install completed!\\033[0m"
+}
+
+# check_metallb validates that METALLB_CONFIG_FILE exists if it is set.
+function check_metallb {
+  if [[ "${METALLB_CONFIG_FILE}" && ! -f "${METALLB_CONFIG_FILE}" ]] ; then
+    echo -e "\\033[33m---> The path ${METALLB_CONFIG_FILE} does not exist...\\033[0m"
+    exit 1
+  fi
+}
+
+# install_network deploys Cilium as the CNI with kube-proxy replacement and BPF masquerade.
+# Usage: install_network [helm-options...]
+#
+# Best practices for production optimization:
+# 1. Jumbo frames, change MTU(9000).
+# 2. Big tcp, enableIPv6BIGTCP/enableIPv4BIGTCP.
+# 3. Set `routingMode=native` and `ipv4NativeRoutingCIDR`.
+# 4. Change `loadBalancer.mode` to dsr(requires `routingMode=native`).
+# 5. Set `loadBalancer.acceleration=native`(requires hardware support).
+#
+# The following is a general configuration without optimization.
+function install_network() {
+  options=${1:-""}
+  echo -e "\\033[32m---> Start install network...\\033[0m"
+  kubernetes_service_host=(`ip -o route get to 8.8.8.8 | sed -n 's/.*src \([0-9.]\+\).*/\1/p'`)
+  helm_upgrade cilium $CHARTS_URL/cilium \
+    --set endpointHealthChecking.enabled=false \
+    --set healthChecking=false \
+    --set operator.replicas=1 \
+    --set sysctlfix.enabled=true \
+    --set bpf.masquerade=true \
+    --set bandwidthManager.enabled=true \
+    --set bandwidthManager.bbr=true \
+    --set kubeProxyReplacement=true \
+    --set hubble.enabled=false \
+    --set hostPort.enabled=true \
+    --set k8sServiceHost=${KUBERNETES_SERVICE_HOST:-$kubernetes_service_host} \
+    --set k8sServicePort=${KUBERNETES_SERVICE_PORT:-6443} \
+    --set prometheus.enabled=true \
+    --set operator.prometheus.enabled=true \
+    --set envoy.enabled=false \
+    --set ipam.operator.clusterPoolIPv4MaskSize=${CLUSTER_POOL_IPV4_MASK_SIZE:-24} \
+    --namespace kube-system $options --wait
+  echo -e "\\033[32m---> Network install completed!\\033[0m"
+}
+
+# install_metallb deploys MetalLB for LoadBalancer service support with L2 advertisement.
+# Usage: install_metallb [helm-options...]
+#   METALLB_CONFIG_FILE - path to a custom IPAddressPool/L2Advertisement manifest;
+#                         uses a default 192.168.254.0/24 pool if unset.
+function install_metallb() {
+  check_metallb
+  options=${1:-""}
+  echo -e "\\033[32m---> Start install metallb...\\033[0m"
+  helm_upgrade metallb $CHARTS_URL/metallb \
+    --set speaker.frr.enabled=true \
+    --namespace metallb \
+    --create-namespace $options --wait
+
+  if [[ -z "${METALLB_CONFIG_FILE}" ]] ; then
+    echo -e "\\033[32m---> Metallb using the default configuration.\\033[0m"
+    kubectl apply -n metallb -f - <<EOF
+apiVersion: metallb.io/v1beta1
+kind: IPAddressPool
+metadata:
+  name: default
+spec:
+  addresses:
+  - 192.168.254.0/24
+
+---
+apiVersion: metallb.io/v1beta1
+kind: L2Advertisement
+metadata:
+  name: drycc-l2-advertisement
+  namespace: metallb
+spec:
+  ipAddressPools:
+  - default
+EOF
+  else
+    kubectl apply -n metallb -f ${METALLB_CONFIG_FILE}
+  fi
+  echo -e "\\033[32m---> Metallb install completed!\\033[0m"
+}
+
+# install_gateway deploys the Istio Gateway API and Istio ingress gateway.
+# Installs: Gateway API CRDs, istio-base, istiod, and istio-gateway.
+# Usage: install_gateway [helm-options...]
+function install_gateway() {
+  options=${1:-""}
+  echo -e "\\033[32m---> Start install gateway...\\033[0m"
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    gateway_api_url=https://github.com/kubernetes-sigs/gateway-api
+  else
+    gateway_api_url=https://github.com/kubernetes-sigs/gateway-api
+  fi
+  version=$(get_latest_github_release "kubernetes-sigs/gateway-api" "^v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$")
+
+  helm repo add istio https://github.com/istio-charts
+  helm repo update
+  kubectl apply -f $gateway_api_url/releases/download/${version}/experimental-install.yaml
+  helm_upgrade istio-base istio/base -n istio-system --set defaultRevision=default --create-namespace --wait $options
+  helm_upgrade istio-istiod istio/istiod -n istio-system \
+    --set pilot.env.PILOT_ENABLE_ALPHA_GATEWAY_API=true \
+    --set pilot.env.PILOT_ENABLE_QUIC_LISTENERS=true \
+    --wait $options
+  helm_upgrade istio-gateway istio/gateway -n istio-gateway \
+    --set service.type=ClusterIP \
+    --create-namespace --wait $options
+  echo -e "\\033[32m---> Gateway install completed!\\033[0m"
+}
+
+# install_cert_manager deploys cert-manager with Gateway API support enabled.
+# Usage: install_cert_manager [helm-options...]
+function install_cert_manager() {
+  options=${1:-""}
+  echo -e "\\033[32m---> Start install cert-manager...\\033[0m"
+  helm_upgrade cert-manager $CHARTS_URL/cert-manager \
+    --namespace cert-manager \
+    --create-namespace \
+    --set clusterResourceNamespace=drycc \
+    --set "extraArgs={--feature-gates=ExperimentalGatewayAPISupport=true}" \
+    --set crds.enabled=true --wait $options
+  echo -e "\\033[32m---> Cert-manager install completed!\\033[0m"
+}
+
+# install_catalog deploys the Kubernetes Service Catalog via Helm.
+# Uses the "canary" image by default; fetches the latest stable version when CHANNEL is "stable".
+# Usage: install_catalog [helm-options...]
+function install_catalog() {
+  service_catalog_version="canary"
+  if [[ "$CHANNEL" == "stable" ]]; then
+    service_catalog_version=$(get_latest_github_release "drycc-addons/service-catalog" "^v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$")
+  fi
+
+  options=${1:-""}
+  echo -e "\\033[32m---> Start install catalog...\\033[0m"
+  helm_upgrade catalog $CHARTS_URL/catalog \
+    --set asyncBindingOperationsEnabled=true \
+    --set image=registry.drycc.cc/drycc-addons/service-catalog:${service_catalog_version#v} \
+    --namespace catalog \
+    --create-namespace --wait $options
+  echo -e "\\033[32m---> Catalog install completed!\\033[0m"
+}
+
+# install_components installs all infrastructure components in order:
+# network (Cilium), metallb, gateway (Istio), cert-manager, catalog.
+function install_components {
+  install_network
+  install_metallb
+  install_gateway
+  install_cert_manager
+  install_catalog
+}
+
+# check_drycc validates that required environment variables are set before installing workflow.
+# Requires: PLATFORM_DOMAIN, DRYCC_ADMIN_USERNAME, DRYCC_ADMIN_PASSWORD.
+function check_drycc {
+  if [[ -z "${PLATFORM_DOMAIN}" ]] ; then
+    echo -e "\\033[33m---> Please set the PLATFORM_DOMAIN variable.\\033[0m"
+    echo -e "\\033[33m---> For example:\\033[0m"
+    echo -e "\\033[33m---> export PLATFORM_DOMAIN=drycc.cc\\033[0m"
+    echo -e "\\033[33m---> And confirm that wildcard domain name resolution has been set.\\033[0m"
+    echo -e "\\033[33m---> For example, the current server IP is 8.8.8.8\\033[0m"
+    echo -e "\\033[33m---> Please point *.drycc.cc to 8.8.8.8\\033[0m"
+    exit 1
+  fi
+
+  if [[ -z "${DRYCC_ADMIN_USERNAME}" || -z "${DRYCC_ADMIN_PASSWORD}" ]] ; then
+    echo -e "\\033[33m---> Please set the DRYCC_ADMIN_USERNAME and DRYCC_ADMIN_PASSWORD variable.\\033[0m"
+    echo -e "\\033[33m---> For example:\\033[0m"
+    echo -e "\\033[33m---> export DRYCC_ADMIN_USERNAME=admin\\033[0m"
+    echo -e "\\033[33m---> export DRYCC_ADMIN_PASSWORD=admin\\033[0m"
+    echo -e "\\033[33m---> This password is used by end users to log in and manage drycc.\\033[0m"
+    echo -e "\\033[33m---> Please set a high security string!!!\\033[0m"
+    exit 1
+  fi
+}
+
+# install_drycc deploys the Drycc Workflow platform via Helm.
+# Usage: install_drycc [helm-options...]
+# Requires: PLATFORM_DOMAIN, DRYCC_ADMIN_USERNAME, DRYCC_ADMIN_PASSWORD.
+# Optional: VICTORIAMETRICS_CONFIG_FILE - path to custom VictoriaMetrics values file.
+function install_drycc {
+  check_drycc
+  options=${1:-""}
+  echo -e "\\033[32m---> Start install workflow...\\033[0m"
+  if [[ "$CHANNEL" == "stable" ]]; then
+    FILER_VERSION=$(get_latest_github_release "drycc/filer" "^v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$")
+    FILER_IMAGE=${DRYCC_REGISTRY}/drycc/filer:$(sed 's#v##' <<< $FILER_VERSION)
+    FILER_IMAGE_PULL_POLICY="IfNotPresent"
+  else
+    FILER_IMAGE=${DRYCC_REGISTRY}/drycc/filer:canary
+    FILER_IMAGE_PULL_POLICY="Always"
+  fi
+
+cat << EOF > "/tmp/drycc-values.yaml"
+global:
+  platformDomain: ${PLATFORM_DOMAIN}
+  certManagerEnabled: ${CERT_MANAGER_ENABLED}
+
+builder:
+  replicas: ${BUILDER_REPLICAS:-1}
+  imageRegistry: ${DRYCC_REGISTRY}
+
+gateway:
+  gatewayClass: ${GATEWAY_CLASS}
+
+quickwit:
+  imageRegistry: ${DRYCC_REGISTRY}
+
+database:
+  imageRegistry: ${DRYCC_REGISTRY}
+  resources:
+    limits:
+      memory: 2512Mi
+      hugepages-2Mi: 256Mi
+  persistence:
+    enabled: true
+    size: ${DATABASE_PERSISTENCE_SIZE:-5Gi}
+    storageClass: ${DATABASE_PERSISTENCE_STORAGE_CLASS:-""}
+
+fluentbit:
+  imageRegistry: ${DRYCC_REGISTRY}
+
+controller:
+  webhookReplicas: ${CONTROLLER_WEBHOOK_REPLICAS:-1}
+  imageRegistry: ${DRYCC_REGISTRY}
+  appRuntimeClass: ${CONTROLLER_APP_RUNTIME_CLASS:-""}
+  appGatewayClass: ${CONTROLLER_APP_GATEWAY_CLASS:-$GATEWAY_CLASS}
+  appStorageClass: ${CONTROLLER_APP_STORAGE_CLASS:-"longhorn"}
+  filerImage: ${FILER_IMAGE}
+  filerImagePullPolicy: ${FILER_IMAGE_PULL_POLICY}
+  api:
+    replicas: ${CONTROLLER_API_REPLICAS:-1}
+  celery:
+    replicas: ${CONTROLLER_CELERY_REPLICAS:-1}
+  metric:
+    replicas: ${CONTROLLER_METRIC_REPLICAS:-1}
+  mutate:
+    replicas: ${CONTROLLER_MUTATE_REPLICAS:-1}
+
+valkey:
+  imageRegistry: ${DRYCC_REGISTRY}
+  persistence:
+    enabled: true
+    size: ${VALKEY_PERSISTENCE_SIZE:-5Gi}
+    storageClass: ${VALKEY_PERSISTENCE_STORAGE_CLASS:-""}
+
+storage:
+  zones: 1
+  drives: 1
+  replicas: 4
+  imageRegistry: ${DRYCC_REGISTRY}
+  persistence:
+    enabled: true
+    size: ${STORAGE_PERSISTENCE_SIZE:-5Gi}
+    storageClass: ${STORAGE_PERSISTENCE_STORAGE_CLASS:-""}
+
+imagebuilder:
+  imageRegistry: ${DRYCC_REGISTRY}
+
+grafana:
+  imageRegistry: ${DRYCC_REGISTRY}
+  persistence:
+    enabled: true
+    size: ${MONITOR_GRAFANA_PERSISTENCE_SIZE:-5Gi}
+    storageClass: ${MONITOR_GRAFANA_PERSISTENCE_STORAGE_CLASS:-""}
+
+passport:
+  replicas: ${PASSPORT_REPLICAS:-1}
+  imageRegistry: ${DRYCC_REGISTRY}
+  adminUsername: ${DRYCC_ADMIN_USERNAME}
+  adminPassword: ${DRYCC_ADMIN_PASSWORD}
+
+registry:
+  replicas: ${REGISTRY_REPLICAS:-1}
+  imageRegistry: ${DRYCC_REGISTRY}
+
+acme:
+  server: ${ACME_SERVER:-"https://acme-v02.api.letsencrypt.org/directory"}
+  externalAccountBinding:
+    keyID: ${ACME_EAB_KEY_ID:-""}
+    keySecret: ${ACME_EAB_KEY_SECRET:-""}
+EOF
+
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    cat << EOF > "/tmp/drycc-mirror-values.yaml"
+imagebuilder:
+  container_registries: |
+    unqualified-search-registries = ["docker.io"]
+    short-name-mode="permissive"
+    [[registry]]
+    prefix = "docker.io"
+    location = "registry-1.docker.io"
+    [[registry.mirror]]
+    prefix = "docker.io"
+    location = "hub-mirror.c.163.com"
+EOF
+  else
+    cat << EOF > "/tmp/drycc-mirror-values.yaml"
+imagebuilder:
+  container_registries: |
+    unqualified-search-registries = ["docker.io"]
+    short-name-mode="permissive"
+EOF
+  fi
+  if [[ -z "${VICTORIAMETRICS_CONFIG_FILE}" ]] ; then
+    VICTORIAMETRICS_CONFIG_FILE="/tmp/drycc-victoriametrics-values.yaml"
+    cat << EOF > "${VICTORIAMETRICS_CONFIG_FILE}"
+victoriametrics:
+  enabled: true
+  vmagent:
+    replicas: 1
+    persistence:
+      enabled: true
+      size: ${VICTORIAMETRICS_VMAGENT_PERSISTENCE_SIZE:-10Gi}
+      storageClass: ${VICTORIAMETRICS_VMAGENT_PERSISTENCE_STORAGE_CLASS:-""}
+  vminsert:
+    replicas: 1
+  vmselect:
+    replicas: 1
+  vmstorage:
+    replicas: 1
+    persistence:
+      enabled: true
+      size: ${VICTORIAMETRICS_VMSTORAGE_PERSISTENCE_SIZE:-10Gi}
+      storageClass: ${VICTORIAMETRICS_VMSTORAGE_PERSISTENCE_STORAGE_CLASS:-""}
+EOF
+    export VICTORIAMETRICS_CONFIG_FILE
+  fi
+
+  helm_upgrade drycc $CHARTS_URL/workflow \
+    --namespace drycc \
+    --values /tmp/drycc-values.yaml \
+    --values /tmp/drycc-mirror-values.yaml \
+    --values ${VICTORIAMETRICS_CONFIG_FILE} \
+    --create-namespace --wait --timeout 30m0s $options
+  echo -e "\\033[32m---> Workflow install completed!\\033[0m"
+}
+
+# install_helmbroker deploys the Helm Broker and registers it as a ClusterServiceBroker.
+# Usage: install_helmbroker [helm-options...]
+#   HELMBROKER_USERNAME - override the auto-generated broker username
+#   HELMBROKER_PASSWORD - override the auto-generated broker password
+function install_helmbroker {
+  if [[ "${INSTALL_DRYCC_MIRROR}" == "cn" ]] ; then
+    addons_base_url="https://github.com/drycc-addons/addons"
+  else
+    addons_base_url="https://github.com/drycc-addons/addons"
+  fi
+  version="latest"
+  if [[ "$CHANNEL" == "stable" ]]; then
+    version=$(get_latest_github_release "drycc-addons/addons" "^v[0-9]+$")
+    version=${version:-latest}
+  fi
+  addons_url="${addons_base_url}/releases/download/${version}/index.yaml"
+
+  options=${1:-""}
+  local VALKEY_PASSWORD=$(kubectl get secrets -n drycc valkey-creds -o jsonpath="{.data.password}"| base64 -d)
+  local HELMBROKER_USERNAME=${HELMBROKER_USERNAME:-$(kubectl get secrets -n drycc-helmbroker helmbroker-creds -o jsonpath="{.data.username}" 2>/dev/null | base64 -d || cat /proc/sys/kernel/random/uuid)}
+  local HELMBROKER_PASSWORD=${HELMBROKER_PASSWORD:-$(kubectl get secrets -n drycc-helmbroker helmbroker-creds -o jsonpath="{.data.password}" 2>/dev/null | base64 -d || cat /proc/sys/kernel/random/uuid)}
+
+  echo -e "\\033[32m---> Start install helmbroker...\\033[0m"
+
+  helm_upgrade helmbroker $CHARTS_URL/helmbroker \
+    --set valkey.enabled=false \
+    --set gateway.gatewayClass=${GATEWAY_CLASS} \
+    --set global.platformDomain=${PLATFORM_DOMAIN} \
+    --set global.certManagerEnabled=${CERT_MANAGER_ENABLED} \
+    --set persistence.size=${HELMBROKER_PERSISTENCE_SIZE:-5Gi} \
+    --set persistence.storageClass=${HELMBROKER_PERSISTENCE_STORAGE_CLASS:-"longhorn"} \
+    --set username=${HELMBROKER_USERNAME} \
+    --set password=${HELMBROKER_PASSWORD} \
+    --set replicas=${HELMBROKER_REPLICAS} \
+    --set valkeyUrl=redis://:${VALKEY_PASSWORD}@drycc-valkey.drycc.svc:16379/11 \
+    --set api.replicas=${HELMBROKER_API_REPLICAS} \
+    --set celery.replicas=${HELMBROKER_CELERY_REPLICAS} \
+    --namespace drycc-helmbroker --create-namespace $options --wait -f - <<EOF
+repositories:
+- name: drycc-helmbroker
+  url: ${addons_url}
+EOF
+
+  kubectl apply -f - <<EOF
+apiVersion: servicecatalog.k8s.io/v1beta1
+kind: ClusterServiceBroker
+metadata:
+  finalizers:
+  - kubernetes-incubator/service-catalog
+  generation: 1
+  labels:
+    app.kubernetes.io/managed-by: Helm
+    heritage: Helm
+  name: helmbroker
+spec:
+  relistBehavior: Duration
+  relistRequests: 5
+  url: http://${HELMBROKER_USERNAME}:${HELMBROKER_PASSWORD}@drycc-helmbroker.drycc-helmbroker.svc
+EOF
+
+  echo -e "\\033[32m---> Helmbroker username: $HELMBROKER_USERNAME\\033[0m"
+  echo -e "\\033[32m---> Helmbroker password: $HELMBROKER_PASSWORD\\033[0m"
+  echo -e "\\033[32m---> Helmbroker install completed!\\033[0m"
+}
+
+# upgrade upgrades all installed components using --reset-then-reuse-values to preserve
+# previous Helm values while applying new chart defaults.
+function upgrade {
+  install_network --reset-then-reuse-values
+  install_metallb --reset-then-reuse-values
+  install_gateway --reset-then-reuse-values
+  install_cert_manager --reset-then-reuse-values
+  install_catalog --reset-then-reuse-values
+  install_longhorn --reset-then-reuse-values
+  install_mountpoint --reset-then-reuse-values
+  install_drycc --reset-then-reuse-values
+  install_helmbroker --reset-then-reuse-values
+  echo -e "\\033[32m---> Upgrade complete, enjoy life...\\033[0m"
+}
+
+export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
+
+if [[ -z "$@" ]] ; then
+  check_drycc
+  check_metallb
+  install_k3s_server
+  install_helm
+  install_components
+  install_longhorn
+  install_mountpoint
+  install_drycc
+  install_helmbroker
+  echo -e "\\033[32m---> Installation complete, enjoy life...\\033[0m"
+else
+  for command in "$@"
+  do
+    $command
+    echo -e "\\033[32m---> Execute $command complete, enjoy life...\\033[0m"
+  done
+fi
diff --git a/uninstall.sh b/uninstall.sh
new file mode 100755
index 00000000..334cb9d7
--- /dev/null
+++ b/uninstall.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+set -eo pipefail
+shopt -s expand_aliases
+
+# clean cilium
+ip link delete cilium_host > /dev/null 2>&1 || true
+ip link delete cilium_net > /dev/null 2>&1 || true
+ip link delete cilium_vxlan > /dev/null 2>&1 || true
+ip link delete nodelocaldns > /dev/null 2>&1 || true
+iptables-save | grep -iv cilium | iptables-restore || true
+ip6tables-save | grep -iv cilium | ip6tables-restore || true
+
+if [[ -x /usr/local/bin/k3s-killall.sh ]] ; then
+    /usr/local/bin/k3s-killall.sh
+fi
+
+if [[ -x /usr/local/bin/k3s-uninstall.sh ]] ; then
+    /usr/local/bin/k3s-uninstall.sh
+fi
+
+if [[ -x /usr/local/bin/k3s-agent-uninstall.sh ]] ; then
+    /usr/local/bin/k3s-agent-uninstall.sh
+fi
+
+if [[ -n "${K3S_DATA_DIR}" ]] ; then
+    rm -rf  "${K3S_DATA_DIR}/rancher"
+fi
+
+iptables -F && iptables -X && iptables -F -t nat && iptables -X -t nat && iptables -P FORWARD ACCEPT
+
+rm -rf /etc/cni
+rm -rf /etc/rancher
+rm -rf /var/lib/rancher
+rm -rf /usr/local/bin/crun
+rm -rf /usr/local/bin/helm ~/.config/helm
+
+rm -rf /opt/kata /usr/local/bin/containerd-shim-kata-v2 /usr/local/bin/kata-collect-data.sh /usr/local/bin/kata-runtime
