diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 7514a4e47..b89d07ab8 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -298,8 +298,14 @@ jobs: ALIYUN_CLUSTER_IMAGE: ${{ steps.prepare.outputs.aliyun_cluster_image }} ALIYUN_LATEST_CLUSTER_IMAGE: ${{ steps.prepare.outputs.aliyun_latest_cluster_image }} run: | - sudo sed -i "s;${OLD_DOCKER_IMAGE};${NEW_DOCKER_IMAGE};" manifests/* - + if [[ -d manifests ]]; then + echo "Found manifests" + sudo sed -i "s;${OLD_DOCKER_IMAGE};${NEW_DOCKER_IMAGE};" manifests/* + fi + if [[ -f charts/${{ env.MODULE_NAME }}/values.yaml ]]; then + echo "Found charts charts/${{ env.MODULE_NAME }}/values.yaml" + sed -i "s;${OLD_DOCKER_IMAGE};${NEW_DOCKER_IMAGE};" charts/${{ env.MODULE_NAME }}/values.yaml + fi # Build for GHCR sudo sealos build -t "${CLUSTER_IMAGE}-amd64" --platform linux/amd64 -f Kubefile sudo sealos build -t "${LATEST_CLUSTER_IMAGE}-amd64" --platform linux/amd64 -f Kubefile diff --git a/.github/workflows/frontends.yml b/.github/workflows/frontends.yml index f0016894d..4f0ec079b 100644 --- a/.github/workflows/frontends.yml +++ b/.github/workflows/frontends.yml @@ -44,6 +44,7 @@ jobs: matrix: module: [ + providers/aiproxy, providers/license, providers/cronjob, providers/template, diff --git a/frontend/providers/aiproxy/Dockerfile b/frontend/providers/aiproxy/Dockerfile new file mode 100644 index 000000000..ac15573a4 --- /dev/null +++ b/frontend/providers/aiproxy/Dockerfile @@ -0,0 +1,58 @@ +# Install dependencies only when needed +FROM node:current-alpine AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat && npm install -g pnpm +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json pnpm-lock.yaml* ./ +RUN \ + [ -f pnpm-lock.yaml ] && pnpm install || \ + (echo "Lockfile not found." && exit 1) + +# Rebuild the source code only when needed +FROM node:current-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +ENV NEXT_TELEMETRY_DISABLED 1 +ENV NEXT_PUBLIC_MOCK_USER '' + +RUN npm install -g pnpm && pnpm run build + +# Production image, copy all the files and run next +FROM node:current-alpine AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +RUN sed -i 's/https/http/' /etc/apk/repositories +RUN apk add curl \ + && apk add ca-certificates \ + && update-ca-certificates + +# You only need to copy next.config.js if you are NOT using the default configuration +# COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json +# COPY --from=builder /app/.env* . + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/frontend/providers/aiproxy/deploy/Kubefile b/frontend/providers/aiproxy/deploy/Kubefile new file mode 100644 index 000000000..eeca014ed --- /dev/null +++ b/frontend/providers/aiproxy/deploy/Kubefile @@ -0,0 +1,3 @@ +FROM scratch +COPY . . +CMD ["bash install.sh"] diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/.helmignore b/frontend/providers/aiproxy/deploy/charts/aiproxy/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/Chart.yaml b/frontend/providers/aiproxy/deploy/charts/aiproxy/Chart.yaml new file mode 100644 index 000000000..1a9182b0e --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: aiproxy-web +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/NOTES.txt b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/NOTES.txt new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/_helpers.tpl b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/_helpers.tpl new file mode 100644 index 000000000..7c2e9b817 --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/_helpers.tpl @@ -0,0 +1,58 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "aiproxy.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "aiproxy.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 "aiproxy.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "aiproxy.labels" -}} +helm.sh/chart: {{ include "aiproxy.chart" . }} +{{ include "aiproxy.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "aiproxy.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aiproxy.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "aiproxy.serviceAccountName" -}} +{{- default (include "aiproxy.fullname" .) .Values.serviceAccount.name }} +{{- end }} diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/app.yaml b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/app.yaml new file mode 100644 index 000000000..1beabe8cc --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/app.yaml @@ -0,0 +1,18 @@ +apiVersion: app.sealos.io/v1 +kind: App +metadata: + name: aiproxy + namespace: app-system +spec: + data: + desc: Sealos AI Proxy + url: https://{{ include "aiproxy.fullname" . }}.{{.Values.cloudDomain}}{{.Values.suffix}} + displayType: more + i18n: + zh: + name: AI Proxy + zh-Hans: + name: AI Proxy + icon: https://objectstorageapi.hzh.sealos.run/cyhipdvv-logos/aiproxy.svg + name: AI Proxy + type: iframe \ No newline at end of file diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/configmap.yaml b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/configmap.yaml new file mode 100644 index 000000000..9987c6156 --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +data: {{ toYaml .Values.aiproxy | nindent 2 }} +kind: ConfigMap +metadata: + namespace: {{.Release.Namespace}} + name: {{ include "aiproxy.fullname" . }} + labels: + {{- include "aiproxy.labels" . | nindent 4 }} diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/deploy.yaml b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/deploy.yaml new file mode 100644 index 000000000..c8f6a119e --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/deploy.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "aiproxy.fullname" . }} + labels: + {{- include "aiproxy.labels" . | nindent 4 }} +spec: + replicas: {{.Values.replicas}} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + selector: + matchLabels: + {{ include "aiproxy.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{ toYaml . | nindent 8 }} + {{- end }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + labels: + {{ include "aiproxy.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{ toYaml . | nindent 8 }} + {{- end }} + spec: + terminationGracePeriodSeconds: 600 + containers: + - image: {{.Values.image}} + imagePullPolicy: IfNotPresent + name: aiproxy + resources: {{ toYaml .Values.resources | nindent 12 }} + env: + - name: AI_PROXY_BACKEND + value: https://aiproxy.{{.Values.cloudDomain}} + envFrom: + - configMapRef: + name: {{ include "aiproxy.fullname" . }} + ports: + - name: http + containerPort: 3000 + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "aiproxy.fullname" . }} + labels: + {{- include "aiproxy.labels" . | nindent 4 }} +spec: + ports: + - port: 3000 + targetPort: 3000 + selector: {{ include "aiproxy.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/ingress.yaml b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/ingress.yaml new file mode 100644 index 000000000..7cb8d3af0 --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/templates/ingress.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + namespace: {{.Release.Namespace}} + name: {{ include "aiproxy.fullname" . }} + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/ssl-redirect: 'false' + nginx.ingress.kubernetes.io/backend-protocol: HTTP + nginx.ingress.kubernetes.io/client-body-buffer-size: 64k + nginx.ingress.kubernetes.io/proxy-buffer-size: 64k + nginx.ingress.kubernetes.io/proxy-send-timeout: '300' + nginx.ingress.kubernetes.io/proxy-read-timeout: '300' + nginx.ingress.kubernetes.io/server-snippet: | + client_header_buffer_size 64k; + large_client_header_buffers 4 128k; +spec: + rules: + - host: {{ include "aiproxy.fullname" . }}.{{.Values.cloudDomain}} + http: + paths: + - pathType: Prefix + path: / + backend: + service: + name: {{ include "aiproxy.fullname" . }} + port: + number: 3000 + tls: + - hosts: + - {{ include "aiproxy.fullname" . }}.{{.Values.cloudDomain}} + secretName: wildcard-cert diff --git a/frontend/providers/aiproxy/deploy/charts/aiproxy/values.yaml b/frontend/providers/aiproxy/deploy/charts/aiproxy/values.yaml new file mode 100644 index 000000000..ad133436e --- /dev/null +++ b/frontend/providers/aiproxy/deploy/charts/aiproxy/values.yaml @@ -0,0 +1,28 @@ +fullnameOverride: aiproxy-web + +replicas: 2 +resources: + requests: + cpu: 50m + memory: 50Mi + limits: + cpu: 500m + memory: 512Mi + +image: ghcr.io/labring/sealos-aiproxy-frontend:latest + +cloudDomain: 127.0.0.1.nip.io +cloudPort: 443 + +suffix: + +aiproxy: + AI_PROXY_BACKEND_KEY: "" + APP_TOKEN_JWT_KEY: "" + AI_PROXY_BACKEND_INTERNAL: "http://aiproxy.apiproxy-system.svc:3000" + ADMIN_NAMESPACES: "ns-admin" + ACCOUNT_SERVER_TOKEN_JWT_KEY: "" + CURRENCY_SYMBOL: "" + ACCOUNT_SERVER: "http://account-service.account-system.svc:2333" + IS_INVITATION_ACTIVE: "false" + DOC_URL: "https://sealos.run/docs/guides/ai-proxy" \ No newline at end of file diff --git a/frontend/providers/aiproxy/deploy/install.sh b/frontend/providers/aiproxy/deploy/install.sh new file mode 100644 index 000000000..1c09c39d8 --- /dev/null +++ b/frontend/providers/aiproxy/deploy/install.sh @@ -0,0 +1,40 @@ +#!/bin/bash +timestamp() { + date +"%Y-%m-%d %T" +} +print() { + flag=$(timestamp) + echo -e "\033[1;32m\033[1m INFO [$flag] >> $* \033[0m" +} +warn() { + flag=$(timestamp) + echo -e "\033[33m WARN [$flag] >> $* \033[0m" +} +info() { + flag=$(timestamp) + echo -e "\033[36m INFO [$flag] >> $* \033[0m" +} +#=========================================================================== +HELM_OPTS=${HELM_OPTS:-""} + +NODE_COUNT=$(kubectl get nodes --no-headers | wc -l) +REPLICA_OPTIONS="" +if [ "$NODE_COUNT" -eq 1 ]; then + REPLICA_OPTIONS="--set replicas=1 " + HELM_OPTS="${HELM_OPTS} ${REPLICA_OPTIONS}" +fi + + +varJwtInternal=$(kubectl get configmap sealos-config -n sealos-system -o jsonpath='{.data.jwtInternal}') +adminKey=$(kubectl get configmap aiproxy-env -n aiproxy-system -o jsonpath='{.data.ADMIN_KEY}' ) +SEALOS_CLOUD_DOMAIN=$(kubectl get configmap sealos-config -n sealos-system -o jsonpath='{.data.cloudDomain}') +SEALOS_CLOUD_PORT=$(kubectl get configmap sealos-config -n sealos-system -o jsonpath='{.data.cloudPort}') +CURRENCY_SYMBOL=$(kubectl get deployment -n dbprovider-frontend dbprovider-frontend -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="CURRENCY_SYMBOL")].value}') +if [[ "${CURRENCY_SYMBOL}" == "usd" ]]; then + SUFFIX="/en/home" +fi + +helm upgrade -i aiproxy-web -n aiproxy-system charts/aiproxy ${HELM_OPTS} \ + --set aiproxy.APP_TOKEN_JWT_KEY=${varJwtInternal} --set aiproxy.AI_PROXY_BACKEND_KEY=${adminKey} \ + --set suffix=${SUFFIX} \ + --set cloudDomain=${SEALOS_CLOUD_DOMAIN} --set cloudPort=${SEALOS_CLOUD_PORT} --set aiproxy.CURRENCY_SYMBOL=${CURRENCY_SYMBOL} --create-namespace