feat: merge service (#14384)
This commit is contained in:
84
.github/actions/deploy/deploy.mjs
vendored
84
.github/actions/deploy/deploy.mjs
vendored
@@ -29,43 +29,26 @@ const isInternal = buildType === 'internal';
|
|||||||
|
|
||||||
const replicaConfig = {
|
const replicaConfig = {
|
||||||
stable: {
|
stable: {
|
||||||
web: 2,
|
front: Number(process.env.PRODUCTION_FRONT_REPLICA) || 2,
|
||||||
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 2,
|
graphql: Number(process.env.PRODUCTION_GRAPHQL_REPLICA) || 2,
|
||||||
sync: Number(process.env.PRODUCTION_SYNC_REPLICA) || 2,
|
|
||||||
renderer: Number(process.env.PRODUCTION_RENDERER_REPLICA) || 2,
|
|
||||||
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 2,
|
doc: Number(process.env.PRODUCTION_DOC_REPLICA) || 2,
|
||||||
},
|
},
|
||||||
beta: {
|
beta: {
|
||||||
web: 1,
|
front: Number(process.env.BETA_FRONT_REPLICA) || 1,
|
||||||
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 1,
|
graphql: Number(process.env.BETA_GRAPHQL_REPLICA) || 1,
|
||||||
sync: Number(process.env.BETA_SYNC_REPLICA) || 1,
|
|
||||||
renderer: Number(process.env.BETA_RENDERER_REPLICA) || 1,
|
|
||||||
doc: Number(process.env.BETA_DOC_REPLICA) || 1,
|
doc: Number(process.env.BETA_DOC_REPLICA) || 1,
|
||||||
},
|
},
|
||||||
canary: {
|
canary: { front: 1, graphql: 1, doc: 1 },
|
||||||
web: 1,
|
|
||||||
graphql: 1,
|
|
||||||
sync: 1,
|
|
||||||
renderer: 1,
|
|
||||||
doc: 1,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cpuConfig = {
|
const cpuConfig = {
|
||||||
beta: {
|
beta: { front: '2', graphql: '1', doc: '1' },
|
||||||
web: '300m',
|
canary: { front: '500m', graphql: '1', doc: '500m' },
|
||||||
graphql: '1',
|
};
|
||||||
sync: '1',
|
|
||||||
doc: '1',
|
const memoryConfig = {
|
||||||
renderer: '300m',
|
beta: { front: '1Gi', graphql: '1Gi', doc: '1Gi' },
|
||||||
},
|
canary: { front: '512Mi', graphql: '512Mi', doc: '512Mi' },
|
||||||
canary: {
|
|
||||||
web: '300m',
|
|
||||||
graphql: '1',
|
|
||||||
sync: '1',
|
|
||||||
doc: '1',
|
|
||||||
renderer: '300m',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createHelmCommand = ({ isDryRun }) => {
|
const createHelmCommand = ({ isDryRun }) => {
|
||||||
@@ -90,16 +73,16 @@ const createHelmCommand = ({ isDryRun }) => {
|
|||||||
`--set-string global.indexer.apiKey="${AFFINE_INDEXER_SEARCH_API_KEY}"`,
|
`--set-string global.indexer.apiKey="${AFFINE_INDEXER_SEARCH_API_KEY}"`,
|
||||||
];
|
];
|
||||||
const serviceAnnotations = [
|
const serviceAnnotations = [
|
||||||
`--set-json web.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
`--set-json front.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
||||||
`--set-json graphql.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
`--set-json graphql.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
||||||
`--set-json sync.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
|
||||||
`--set-json doc.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
`--set-json doc.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${APP_IAM_ACCOUNT}\\" }"`,
|
||||||
].concat(
|
].concat(
|
||||||
isProduction || isBeta || isInternal
|
isProduction || isBeta || isInternal
|
||||||
? [
|
? [
|
||||||
`--set-json web.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
|
`--set-json front.services.web.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
|
||||||
|
`--set-json front.services.sync.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
|
||||||
|
`--set-json front.services.renderer.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
|
||||||
`--set-json graphql.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
|
`--set-json graphql.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
|
||||||
`--set-json sync.service.annotations="{ \\"cloud.google.com/neg\\": \\"{\\\\\\"ingress\\\\\\": true}\\" }"`,
|
|
||||||
`--set-json cloud-sql-proxy.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_SQL_IAM_ACCOUNT}\\" }"`,
|
`--set-json cloud-sql-proxy.serviceAccount.annotations="{ \\"iam.gke.io/gcp-service-account\\": \\"${CLOUD_SQL_IAM_ACCOUNT}\\" }"`,
|
||||||
`--set-json cloud-sql-proxy.nodeSelector="{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\" }"`,
|
`--set-json cloud-sql-proxy.nodeSelector="{ \\"iam.gke.io/gke-metadata-server-enabled\\": \\"true\\" }"`,
|
||||||
]
|
]
|
||||||
@@ -107,14 +90,22 @@ const createHelmCommand = ({ isDryRun }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const cpu = cpuConfig[buildType];
|
const cpu = cpuConfig[buildType];
|
||||||
const resources = cpu
|
const memory = memoryConfig[buildType];
|
||||||
? [
|
let resources = [];
|
||||||
`--set web.resources.requests.cpu="${cpu.web}"`,
|
if (cpu) {
|
||||||
`--set graphql.resources.requests.cpu="${cpu.graphql}"`,
|
resources = resources.concat([
|
||||||
`--set sync.resources.requests.cpu="${cpu.sync}"`,
|
`--set front.resources.requests.cpu="${cpu.front}"`,
|
||||||
`--set doc.resources.requests.cpu="${cpu.doc}"`,
|
`--set graphql.resources.requests.cpu="${cpu.graphql}"`,
|
||||||
]
|
`--set doc.resources.requests.cpu="${cpu.doc}"`,
|
||||||
: [];
|
]);
|
||||||
|
}
|
||||||
|
if (memory) {
|
||||||
|
resources = resources.concat([
|
||||||
|
`--set front.resources.requests.memory="${memory.front}"`,
|
||||||
|
`--set graphql.resources.requests.memory="${memory.graphql}"`,
|
||||||
|
`--set doc.resources.requests.memory="${memory.doc}"`,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
const replica = replicaConfig[buildType] || replicaConfig.canary;
|
const replica = replicaConfig[buildType] || replicaConfig.canary;
|
||||||
|
|
||||||
@@ -130,6 +121,7 @@ const createHelmCommand = ({ isDryRun }) => {
|
|||||||
.split(',')
|
.split(',')
|
||||||
.map(host => host.trim())
|
.map(host => host.trim())
|
||||||
.filter(host => host);
|
.filter(host => host);
|
||||||
|
const primaryHost = hosts[0] || '0.0.0.0';
|
||||||
const deployCommand = [
|
const deployCommand = [
|
||||||
`helm upgrade --install affine .github/helm/affine`,
|
`helm upgrade --install affine .github/helm/affine`,
|
||||||
`--namespace ${namespace}`,
|
`--namespace ${namespace}`,
|
||||||
@@ -144,18 +136,14 @@ const createHelmCommand = ({ isDryRun }) => {
|
|||||||
`--set-string global.version="${APP_VERSION}"`,
|
`--set-string global.version="${APP_VERSION}"`,
|
||||||
...redisAndPostgres,
|
...redisAndPostgres,
|
||||||
...indexerOptions,
|
...indexerOptions,
|
||||||
`--set web.replicaCount=${replica.web}`,
|
`--set front.replicaCount=${replica.front}`,
|
||||||
`--set-string web.image.tag="${imageTag}"`,
|
`--set-string front.image.tag="${imageTag}"`,
|
||||||
|
`--set-string front.app.host="${primaryHost}"`,
|
||||||
`--set graphql.replicaCount=${replica.graphql}`,
|
`--set graphql.replicaCount=${replica.graphql}`,
|
||||||
`--set-string graphql.image.tag="${imageTag}"`,
|
`--set-string graphql.image.tag="${imageTag}"`,
|
||||||
`--set graphql.app.host=${hosts[0]}`,
|
`--set-string graphql.app.host="${primaryHost}"`,
|
||||||
`--set sync.replicaCount=${replica.sync}`,
|
|
||||||
`--set-string sync.image.tag="${imageTag}"`,
|
|
||||||
`--set-string renderer.image.tag="${imageTag}"`,
|
|
||||||
`--set renderer.app.host=${hosts[0]}`,
|
|
||||||
`--set renderer.replicaCount=${replica.renderer}`,
|
|
||||||
`--set-string doc.image.tag="${imageTag}"`,
|
`--set-string doc.image.tag="${imageTag}"`,
|
||||||
`--set doc.app.host=${hosts[0]}`,
|
`--set-string doc.app.host="${primaryHost}"`,
|
||||||
`--set doc.replicaCount=${replica.doc}`,
|
`--set doc.replicaCount=${replica.doc}`,
|
||||||
...serviceAnnotations,
|
...serviceAnnotations,
|
||||||
...resources,
|
...resources,
|
||||||
|
|||||||
13
.github/deployment/front/Dockerfile
vendored
13
.github/deployment/front/Dockerfile
vendored
@@ -1,13 +0,0 @@
|
|||||||
FROM openresty/openresty:1.27.1.1-0-buster
|
|
||||||
WORKDIR /app
|
|
||||||
COPY ./packages/frontend/apps/web/dist ./dist
|
|
||||||
COPY ./packages/frontend/admin/dist ./admin
|
|
||||||
COPY ./packages/frontend/apps/mobile/dist ./mobile
|
|
||||||
COPY ./.github/deployment/front/nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
|
|
||||||
COPY ./.github/deployment/front/affine.nginx.conf /etc/nginx/conf.d/affine.nginx.conf
|
|
||||||
|
|
||||||
RUN mkdir -p /var/log/nginx && \
|
|
||||||
rm /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]
|
|
||||||
42
.github/deployment/front/affine.nginx.conf
vendored
42
.github/deployment/front/affine.nginx.conf
vendored
@@ -1,42 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 8080;
|
|
||||||
location /admin {
|
|
||||||
root /app/;
|
|
||||||
index index.html;
|
|
||||||
try_files $uri/index.html $uri/ $uri /admin/index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
set $app_root_path /app/dist/;
|
|
||||||
set $mobile_root /app/dist/;
|
|
||||||
set_by_lua $affine_env 'return os.getenv("AFFINE_ENV")';
|
|
||||||
|
|
||||||
if ($affine_env = "dev") {
|
|
||||||
set $mobile_root /app/mobile/;
|
|
||||||
}
|
|
||||||
|
|
||||||
# https://gist.github.com/mariusom/6683dc52b1cad1a1f372e908bdb209d0
|
|
||||||
if ($http_user_agent ~* "(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino") {
|
|
||||||
set $app_root_path $mobile_root;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($http_user_agent ~* "^(1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-)") {
|
|
||||||
set $app_root_path $mobile_root;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/(_plugin|assets|imgs|js|plugins|static)/ {
|
|
||||||
root $app_root_path;
|
|
||||||
try_files $uri $uri/ =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root $app_root_path;
|
|
||||||
index index.html;
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
add_header Cache-Control "private, no-cache, no-store, max-age=0, must-revalidate";
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 404 /404.html;
|
|
||||||
location = /404.html {
|
|
||||||
internal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
.github/deployment/front/nginx.conf
vendored
15
.github/deployment/front/nginx.conf
vendored
@@ -1,15 +0,0 @@
|
|||||||
worker_processes 4;
|
|
||||||
error_log /var/log/nginx/error.log warn;
|
|
||||||
pcre_jit on;
|
|
||||||
env AFFINE_ENV;
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
http {
|
|
||||||
include mime.types;
|
|
||||||
log_format main '$remote_addr [$time_local] "$request" '
|
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
||||||
access_log /var/log/nginx/access.log main;
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: sync
|
name: front
|
||||||
description: AFFiNE Sync Server
|
description: AFFiNE front server
|
||||||
type: application
|
type: application
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
appVersion: "0.26.0"
|
appVersion: "0.26.0"
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
1. Get the application URL by running these commands:
|
1. Get the application URL by running these commands:
|
||||||
{{- if contains "NodePort" .Values.service.type }}
|
{{- if contains "NodePort" .Values.services.sync.type }}
|
||||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "renderer.fullname" . }})
|
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ .Values.services.sync.name }})
|
||||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||||
echo http://$NODE_IP:$NODE_PORT
|
echo http://$NODE_IP:$NODE_PORT
|
||||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
{{- else if contains "LoadBalancer" .Values.services.sync.type }}
|
||||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "renderer.fullname" . }}'
|
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ .Values.services.sync.name }}'
|
||||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "renderer.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ .Values.services.sync.name }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
echo http://$SERVICE_IP:{{ .Values.services.sync.port }}
|
||||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
{{- else if contains "ClusterIP" .Values.services.sync.type }}
|
||||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "renderer.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "front.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{/*
|
{{/*
|
||||||
Expand the name of the chart.
|
Expand the name of the chart.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "sync.name" -}}
|
{{- define "front.name" -}}
|
||||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ 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).
|
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.
|
If release name contains chart name it will be used as a full name.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "sync.fullname" -}}
|
{{- define "front.fullname" -}}
|
||||||
{{- if .Values.fullnameOverride }}
|
{{- if .Values.fullnameOverride }}
|
||||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
@@ -26,16 +26,16 @@ If release name contains chart name it will be used as a full name.
|
|||||||
{{/*
|
{{/*
|
||||||
Create chart name and version as used by the chart label.
|
Create chart name and version as used by the chart label.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "sync.chart" -}}
|
{{- define "front.chart" -}}
|
||||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Common labels
|
Common labels
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "sync.labels" -}}
|
{{- define "front.labels" -}}
|
||||||
helm.sh/chart: {{ include "sync.chart" . }}
|
helm.sh/chart: {{ include "front.chart" . }}
|
||||||
{{ include "sync.selectorLabels" . }}
|
{{ include "front.selectorLabels" . }}
|
||||||
{{- if .Chart.AppVersion }}
|
{{- if .Chart.AppVersion }}
|
||||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -46,17 +46,17 @@ monitoring: enabled
|
|||||||
{{/*
|
{{/*
|
||||||
Selector labels
|
Selector labels
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "sync.selectorLabels" -}}
|
{{- define "front.selectorLabels" -}}
|
||||||
app.kubernetes.io/name: {{ include "sync.name" . }}
|
app.kubernetes.io/name: {{ include "front.name" . }}
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Create the name of the service account to use
|
Create the name of the service account to use
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "sync.serviceAccountName" -}}
|
{{- define "front.serviceAccountName" -}}
|
||||||
{{- if .Values.serviceAccount.create }}
|
{{- if .Values.serviceAccount.create }}
|
||||||
{{- default (include "sync.fullname" .) .Values.serviceAccount.name }}
|
{{- default (include "front.fullname" .) .Values.serviceAccount.name }}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
{{- default "default" .Values.serviceAccount.name }}
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
120
.github/helm/affine/charts/front/templates/deployment.yaml
vendored
Normal file
120
.github/helm/affine/charts/front/templates/deployment.yaml
vendored
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "front.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "front.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "front.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "front.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ include "front.serviceAccountName" . }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
env:
|
||||||
|
- name: AFFINE_PRIVATE_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: "{{ .Values.global.secret.secretName }}"
|
||||||
|
key: key
|
||||||
|
- name: NODE_ENV
|
||||||
|
value: "{{ .Values.env }}"
|
||||||
|
- name: NODE_OPTIONS
|
||||||
|
value: "{{ .Values.nodeOptions }}"
|
||||||
|
- name: NO_COLOR
|
||||||
|
value: "1"
|
||||||
|
- name: DEPLOYMENT_TYPE
|
||||||
|
value: "{{ .Values.global.deployment.type }}"
|
||||||
|
- name: DEPLOYMENT_PLATFORM
|
||||||
|
value: "{{ .Values.global.deployment.platform }}"
|
||||||
|
- name: SERVER_FLAVOR
|
||||||
|
value: "front"
|
||||||
|
- name: AFFINE_ENV
|
||||||
|
value: "{{ .Release.Namespace }}"
|
||||||
|
- name: DATABASE_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: pg-postgresql
|
||||||
|
key: postgres-password
|
||||||
|
- name: DATABASE_URL
|
||||||
|
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.host }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
|
||||||
|
- name: REDIS_SERVER_ENABLED
|
||||||
|
value: "true"
|
||||||
|
- name: REDIS_SERVER_HOST
|
||||||
|
value: "{{ .Values.global.redis.host }}"
|
||||||
|
- name: REDIS_SERVER_PORT
|
||||||
|
value: "{{ .Values.global.redis.port }}"
|
||||||
|
- name: REDIS_SERVER_USER
|
||||||
|
value: "{{ .Values.global.redis.username }}"
|
||||||
|
- name: REDIS_SERVER_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: redis
|
||||||
|
key: redis-password
|
||||||
|
- name: REDIS_SERVER_DATABASE
|
||||||
|
value: "{{ .Values.global.redis.database }}"
|
||||||
|
- name: AFFINE_INDEXER_SEARCH_PROVIDER
|
||||||
|
value: "{{ .Values.global.indexer.provider }}"
|
||||||
|
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
||||||
|
value: "{{ .Values.global.indexer.endpoint }}"
|
||||||
|
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: indexer
|
||||||
|
key: indexer-apiKey
|
||||||
|
- name: AFFINE_SERVER_PORT
|
||||||
|
value: "{{ .Values.app.port }}"
|
||||||
|
- name: AFFINE_SERVER_SUB_PATH
|
||||||
|
value: "{{ .Values.app.path }}"
|
||||||
|
- name: AFFINE_SERVER_HOST
|
||||||
|
value: "{{ .Values.app.host }}"
|
||||||
|
- name: AFFINE_SERVER_HTTPS
|
||||||
|
value: "{{ .Values.app.https }}"
|
||||||
|
- name: DOC_SERVICE_ENDPOINT
|
||||||
|
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ .Values.app.port }}
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /info
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /info
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
19
.github/helm/affine/charts/front/templates/service-renderer.yaml
vendored
Normal file
19
.github/helm/affine/charts/front/templates/service-renderer.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.services.renderer.name }}
|
||||||
|
labels:
|
||||||
|
{{- include "front.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.services.renderer.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.services.renderer.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.services.renderer.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "front.selectorLabels" . | nindent 4 }}
|
||||||
19
.github/helm/affine/charts/front/templates/service-sync.yaml
vendored
Normal file
19
.github/helm/affine/charts/front/templates/service-sync.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.services.sync.name }}
|
||||||
|
labels:
|
||||||
|
{{- include "front.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.services.sync.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.services.sync.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.services.sync.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "front.selectorLabels" . | nindent 4 }}
|
||||||
19
.github/helm/affine/charts/front/templates/service-web.yaml
vendored
Normal file
19
.github/helm/affine/charts/front/templates/service-web.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.services.web.name }}
|
||||||
|
labels:
|
||||||
|
{{- include "front.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.services.web.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.services.web.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.services.web.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "front.selectorLabels" . | nindent 4 }}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ include "web.serviceAccountName" . }}
|
name: {{ include "front.serviceAccountName" . }}
|
||||||
labels:
|
labels:
|
||||||
{{- include "web.labels" . | nindent 4 }}
|
{{- include "front.labels" . | nindent 4 }}
|
||||||
{{- with .Values.serviceAccount.annotations }}
|
{{- with .Values.serviceAccount.annotations }}
|
||||||
annotations:
|
annotations:
|
||||||
{{- toYaml . | nindent 4 }}
|
{{- toYaml . | nindent 4 }}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
name: "{{ include "web.fullname" . }}-test-connection"
|
name: "{{ include "front.fullname" . }}-test-connection"
|
||||||
labels:
|
labels:
|
||||||
{{- include "web.labels" . | nindent 4 }}
|
{{- include "front.labels" . | nindent 4 }}
|
||||||
annotations:
|
annotations:
|
||||||
"helm.sh/hook": test
|
"helm.sh/hook": test
|
||||||
spec:
|
spec:
|
||||||
@@ -11,5 +11,5 @@ spec:
|
|||||||
- name: wget
|
- name: wget
|
||||||
image: busybox
|
image: busybox
|
||||||
command: ['wget']
|
command: ['wget']
|
||||||
args: ['{{ include "web.fullname" . }}:{{ .Values.service.port }}']
|
args: ['{{ .Values.services.sync.name }}:{{ .Values.services.sync.port }}']
|
||||||
restartPolicy: Never
|
restartPolicy: Never
|
||||||
60
.github/helm/affine/charts/front/values.yaml
vendored
Normal file
60
.github/helm/affine/charts/front/values.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
replicaCount: 1
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/toeverything/affine
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
tag: ''
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: ''
|
||||||
|
fullnameOverride: ''
|
||||||
|
# map to NODE_ENV environment variable
|
||||||
|
env: 'production'
|
||||||
|
nodeOptions: '--max-old-space-size=3072'
|
||||||
|
app:
|
||||||
|
# AFFINE_SERVER_PORT
|
||||||
|
port: 3010
|
||||||
|
# AFFINE_SERVER_SUB_PATH
|
||||||
|
path: ''
|
||||||
|
# AFFINE_SERVER_HOST
|
||||||
|
host: '0.0.0.0'
|
||||||
|
https: true
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
annotations: {}
|
||||||
|
name: 'affine-front'
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext:
|
||||||
|
fsGroup: 2000
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: '2'
|
||||||
|
memory: 4Gi
|
||||||
|
|
||||||
|
probe:
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
|
||||||
|
services:
|
||||||
|
sync:
|
||||||
|
name: affine-sync
|
||||||
|
type: ClusterIP
|
||||||
|
port: 3010
|
||||||
|
annotations:
|
||||||
|
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
||||||
|
renderer:
|
||||||
|
name: affine-renderer
|
||||||
|
type: ClusterIP
|
||||||
|
port: 3000
|
||||||
|
annotations:
|
||||||
|
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
||||||
|
web:
|
||||||
|
name: affine-web
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8080
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
affinity: {}
|
||||||
11
.github/helm/affine/charts/renderer/Chart.yaml
vendored
11
.github/helm/affine/charts/renderer/Chart.yaml
vendored
@@ -1,11 +0,0 @@
|
|||||||
apiVersion: v2
|
|
||||||
name: renderer
|
|
||||||
description: AFFiNE renderer server
|
|
||||||
type: application
|
|
||||||
version: 0.0.0
|
|
||||||
appVersion: "0.26.0"
|
|
||||||
dependencies:
|
|
||||||
- name: gcloud-sql-proxy
|
|
||||||
version: 0.0.0
|
|
||||||
repository: "file://../gcloud-sql-proxy"
|
|
||||||
condition: .global.database.gcloud.enabled
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
{{/*
|
|
||||||
Expand the name of the chart.
|
|
||||||
*/}}
|
|
||||||
{{- define "renderer.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 "renderer.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 "renderer.chart" -}}
|
|
||||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Common labels
|
|
||||||
*/}}
|
|
||||||
{{- define "renderer.labels" -}}
|
|
||||||
helm.sh/chart: {{ include "renderer.chart" . }}
|
|
||||||
{{ include "renderer.selectorLabels" . }}
|
|
||||||
{{- if .Chart.AppVersion }}
|
|
||||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
||||||
{{- end }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
monitoring: enabled
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Selector labels
|
|
||||||
*/}}
|
|
||||||
{{- define "renderer.selectorLabels" -}}
|
|
||||||
app.kubernetes.io/name: {{ include "renderer.name" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create the name of the service account to use
|
|
||||||
*/}}
|
|
||||||
{{- define "renderer.serviceAccountName" -}}
|
|
||||||
{{- if .Values.serviceAccount.create }}
|
|
||||||
{{- default (include "renderer.fullname" .) .Values.serviceAccount.name }}
|
|
||||||
{{- else }}
|
|
||||||
{{- default "default" .Values.serviceAccount.name }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "renderer.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "renderer.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
replicas: {{ .Values.replicaCount }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "renderer.selectorLabels" . | nindent 6 }}
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
{{- with .Values.podAnnotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
labels:
|
|
||||||
{{- include "renderer.selectorLabels" . | nindent 8 }}
|
|
||||||
spec:
|
|
||||||
{{- with .Values.imagePullSecrets }}
|
|
||||||
imagePullSecrets:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
serviceAccountName: {{ include "renderer.serviceAccountName" . }}
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
env:
|
|
||||||
- name: AFFINE_PRIVATE_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: "{{ .Values.global.secret.secretName }}"
|
|
||||||
key: key
|
|
||||||
- name: NODE_ENV
|
|
||||||
value: "{{ .Values.env }}"
|
|
||||||
- name: NODE_OPTIONS
|
|
||||||
value: "--max-old-space-size=2048"
|
|
||||||
- name: NO_COLOR
|
|
||||||
value: "1"
|
|
||||||
- name: DEPLOYMENT_TYPE
|
|
||||||
value: "{{ .Values.global.deployment.type }}"
|
|
||||||
- name: DEPLOYMENT_PLATFORM
|
|
||||||
value: "{{ .Values.global.deployment.platform }}"
|
|
||||||
- name: SERVER_FLAVOR
|
|
||||||
value: "renderer"
|
|
||||||
- name: AFFINE_ENV
|
|
||||||
value: "{{ .Release.Namespace }}"
|
|
||||||
- name: DATABASE_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: pg-postgresql
|
|
||||||
key: postgres-password
|
|
||||||
- name: DATABASE_URL
|
|
||||||
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.host }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
|
|
||||||
- name: REDIS_SERVER_ENABLED
|
|
||||||
value: "true"
|
|
||||||
- name: REDIS_SERVER_HOST
|
|
||||||
value: "{{ .Values.global.redis.host }}"
|
|
||||||
- name: REDIS_SERVER_PORT
|
|
||||||
value: "{{ .Values.global.redis.port }}"
|
|
||||||
- name: REDIS_SERVER_USER
|
|
||||||
value: "{{ .Values.global.redis.username }}"
|
|
||||||
- name: REDIS_SERVER_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: redis
|
|
||||||
key: redis-password
|
|
||||||
- name: REDIS_SERVER_DATABASE
|
|
||||||
value: "{{ .Values.global.redis.database }}"
|
|
||||||
- name: AFFINE_INDEXER_SEARCH_PROVIDER
|
|
||||||
value: "{{ .Values.global.indexer.provider }}"
|
|
||||||
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
|
||||||
value: "{{ .Values.global.indexer.endpoint }}"
|
|
||||||
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: indexer
|
|
||||||
key: indexer-apiKey
|
|
||||||
- name: AFFINE_SERVER_PORT
|
|
||||||
value: "{{ .Values.service.port }}"
|
|
||||||
- name: AFFINE_SERVER_SUB_PATH
|
|
||||||
value: "{{ .Values.app.path }}"
|
|
||||||
- name: AFFINE_SERVER_HOST
|
|
||||||
value: "{{ .Values.app.host }}"
|
|
||||||
- name: AFFINE_SERVER_HTTPS
|
|
||||||
value: "{{ .Values.app.https }}"
|
|
||||||
- name: DOC_SERVICE_ENDPOINT
|
|
||||||
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: {{ .Values.service.port }}
|
|
||||||
protocol: TCP
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /info
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /info
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
|
||||||
{{- with .Values.nodeSelector }}
|
|
||||||
nodeSelector:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.affinity }}
|
|
||||||
affinity:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.tolerations }}
|
|
||||||
tolerations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "graphql.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "graphql.labels" . | nindent 4 }}
|
|
||||||
{{- with .Values.service.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
type: {{ .Values.service.type }}
|
|
||||||
ports:
|
|
||||||
- port: {{ .Values.service.port }}
|
|
||||||
targetPort: http
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
selector:
|
|
||||||
{{- include "graphql.selectorLabels" . | nindent 4 }}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{{- if .Values.serviceAccount.create -}}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: {{ include "graphql.serviceAccountName" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "graphql.labels" . | nindent 4 }}
|
|
||||||
{{- with .Values.serviceAccount.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: "{{ include "renderer.fullname" . }}-test-connection"
|
|
||||||
labels:
|
|
||||||
{{- include "renderer.labels" . | nindent 4 }}
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": test
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: wget
|
|
||||||
image: busybox
|
|
||||||
command: ['wget']
|
|
||||||
args: ['{{ include "renderer.fullname" . }}:{{ .Values.service.port }}']
|
|
||||||
restartPolicy: Never
|
|
||||||
38
.github/helm/affine/charts/renderer/values.yaml
vendored
38
.github/helm/affine/charts/renderer/values.yaml
vendored
@@ -1,38 +0,0 @@
|
|||||||
replicaCount: 1
|
|
||||||
image:
|
|
||||||
repository: ghcr.io/toeverything/affine
|
|
||||||
pullPolicy: IfNotPresent
|
|
||||||
tag: ''
|
|
||||||
|
|
||||||
imagePullSecrets: []
|
|
||||||
nameOverride: ''
|
|
||||||
fullnameOverride: ''
|
|
||||||
# map to NODE_ENV environment variable
|
|
||||||
env: 'production'
|
|
||||||
app:
|
|
||||||
# AFFINE_SERVER_SUB_PATH
|
|
||||||
path: ''
|
|
||||||
# AFFINE_SERVER_HOST
|
|
||||||
host: '0.0.0.0'
|
|
||||||
https: true
|
|
||||||
serviceAccount:
|
|
||||||
create: true
|
|
||||||
annotations: {}
|
|
||||||
name: 'affine-renderer'
|
|
||||||
|
|
||||||
podAnnotations: {}
|
|
||||||
|
|
||||||
podSecurityContext:
|
|
||||||
fsGroup: 2000
|
|
||||||
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: '1'
|
|
||||||
memory: 2Gi
|
|
||||||
|
|
||||||
probe:
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
|
|
||||||
nodeSelector: {}
|
|
||||||
tolerations: []
|
|
||||||
affinity: {}
|
|
||||||
23
.github/helm/affine/charts/sync/.helmignore
vendored
23
.github/helm/affine/charts/sync/.helmignore
vendored
@@ -1,23 +0,0 @@
|
|||||||
# 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/
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
1. Get the application URL by running these commands:
|
|
||||||
{{- if contains "NodePort" .Values.service.type }}
|
|
||||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "sync.fullname" . }})
|
|
||||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
|
||||||
echo http://$NODE_IP:$NODE_PORT
|
|
||||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
|
||||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
|
||||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "sync.fullname" . }}'
|
|
||||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "sync.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
|
||||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
|
||||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
|
||||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "sync.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
|
||||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
|
||||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
|
||||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "sync.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "sync.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
replicas: {{ .Values.replicaCount }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "sync.selectorLabels" . | nindent 6 }}
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
{{- with .Values.podAnnotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
labels:
|
|
||||||
{{- include "sync.selectorLabels" . | nindent 8 }}
|
|
||||||
spec:
|
|
||||||
{{- with .Values.imagePullSecrets }}
|
|
||||||
imagePullSecrets:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
serviceAccountName: {{ include "sync.serviceAccountName" . }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
env:
|
|
||||||
- name: AFFINE_PRIVATE_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: "{{ .Values.global.secret.secretName }}"
|
|
||||||
key: key
|
|
||||||
- name: NODE_ENV
|
|
||||||
value: "{{ .Values.env }}"
|
|
||||||
- name: NO_COLOR
|
|
||||||
value: "1"
|
|
||||||
- name: DEPLOYMENT_TYPE
|
|
||||||
value: "{{ .Values.global.deployment.type }}"
|
|
||||||
- name: DEPLOYMENT_PLATFORM
|
|
||||||
value: "{{ .Values.global.deployment.platform }}"
|
|
||||||
- name: SERVER_FLAVOR
|
|
||||||
value: "sync"
|
|
||||||
- name: AFFINE_ENV
|
|
||||||
value: "{{ .Release.Namespace }}"
|
|
||||||
- name: DATABASE_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: pg-postgresql
|
|
||||||
key: postgres-password
|
|
||||||
- name: DATABASE_URL
|
|
||||||
value: postgres://{{ .Values.global.database.user }}:$(DATABASE_PASSWORD)@{{ .Values.global.database.host }}:{{ .Values.global.database.port }}/{{ .Values.global.database.name }}
|
|
||||||
- name: REDIS_SERVER_HOST
|
|
||||||
value: "{{ .Values.global.redis.host }}"
|
|
||||||
- name: REDIS_SERVER_PORT
|
|
||||||
value: "{{ .Values.global.redis.port }}"
|
|
||||||
- name: REDIS_SERVER_USER
|
|
||||||
value: "{{ .Values.global.redis.username }}"
|
|
||||||
- name: REDIS_SERVER_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: redis
|
|
||||||
key: redis-password
|
|
||||||
- name: REDIS_SERVER_DATABASE
|
|
||||||
value: "{{ .Values.global.redis.database }}"
|
|
||||||
- name: AFFINE_INDEXER_SEARCH_PROVIDER
|
|
||||||
value: "{{ .Values.global.indexer.provider }}"
|
|
||||||
- name: AFFINE_INDEXER_SEARCH_ENDPOINT
|
|
||||||
value: "{{ .Values.global.indexer.endpoint }}"
|
|
||||||
- name: AFFINE_INDEXER_SEARCH_API_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: indexer
|
|
||||||
key: indexer-apiKey
|
|
||||||
- name: AFFINE_SERVER_PORT
|
|
||||||
value: "{{ .Values.service.port }}"
|
|
||||||
- name: AFFINE_SERVER_HOST
|
|
||||||
value: "{{ .Values.app.host }}"
|
|
||||||
- name: DOC_SERVICE_ENDPOINT
|
|
||||||
value: "http://{{ .Values.global.docService.name }}:{{ .Values.global.docService.port }}"
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: {{ .Values.service.port }}
|
|
||||||
protocol: TCP
|
|
||||||
livenessProbe:
|
|
||||||
tcpSocket:
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
|
||||||
readinessProbe:
|
|
||||||
tcpSocket:
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
|
||||||
{{- with .Values.nodeSelector }}
|
|
||||||
nodeSelector:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.affinity }}
|
|
||||||
affinity:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.tolerations }}
|
|
||||||
tolerations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "sync.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "sync.labels" . | nindent 4 }}
|
|
||||||
{{- with .Values.service.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
type: {{ .Values.service.type }}
|
|
||||||
ports:
|
|
||||||
- port: {{ .Values.service.port }}
|
|
||||||
targetPort: http
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
selector:
|
|
||||||
{{- include "sync.selectorLabels" . | nindent 4 }}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{{- if .Values.serviceAccount.create -}}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: {{ include "sync.serviceAccountName" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "sync.labels" . | nindent 4 }}
|
|
||||||
{{- with .Values.serviceAccount.annotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 4 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Pod
|
|
||||||
metadata:
|
|
||||||
name: "{{ include "sync.fullname" . }}-test-connection"
|
|
||||||
labels:
|
|
||||||
{{- include "sync.labels" . | nindent 4 }}
|
|
||||||
annotations:
|
|
||||||
"helm.sh/hook": test
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: wget
|
|
||||||
image: busybox
|
|
||||||
command: ['wget']
|
|
||||||
args: ['{{ include "sync.fullname" . }}:{{ .Values.service.port }}']
|
|
||||||
restartPolicy: Never
|
|
||||||
38
.github/helm/affine/charts/sync/values.yaml
vendored
38
.github/helm/affine/charts/sync/values.yaml
vendored
@@ -1,38 +0,0 @@
|
|||||||
replicaCount: 1
|
|
||||||
image:
|
|
||||||
repository: ghcr.io/toeverything/affine
|
|
||||||
pullPolicy: IfNotPresent
|
|
||||||
tag: ''
|
|
||||||
|
|
||||||
imagePullSecrets: []
|
|
||||||
nameOverride: ''
|
|
||||||
fullnameOverride: ''
|
|
||||||
# map to NODE_ENV environment variable
|
|
||||||
env: 'production'
|
|
||||||
app:
|
|
||||||
# AFFINE_SERVER_HOST
|
|
||||||
host: '0.0.0.0'
|
|
||||||
serviceAccount:
|
|
||||||
create: true
|
|
||||||
annotations: {}
|
|
||||||
name: 'affine-sync'
|
|
||||||
|
|
||||||
podAnnotations: {}
|
|
||||||
|
|
||||||
podSecurityContext:
|
|
||||||
fsGroup: 2000
|
|
||||||
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: '2'
|
|
||||||
memory: 4Gi
|
|
||||||
requests:
|
|
||||||
cpu: '1'
|
|
||||||
memory: 2Gi
|
|
||||||
|
|
||||||
probe:
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
|
|
||||||
nodeSelector: {}
|
|
||||||
tolerations: []
|
|
||||||
affinity: {}
|
|
||||||
23
.github/helm/affine/charts/web/.helmignore
vendored
23
.github/helm/affine/charts/web/.helmignore
vendored
@@ -1,23 +0,0 @@
|
|||||||
# 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/
|
|
||||||
6
.github/helm/affine/charts/web/Chart.yaml
vendored
6
.github/helm/affine/charts/web/Chart.yaml
vendored
@@ -1,6 +0,0 @@
|
|||||||
apiVersion: v2
|
|
||||||
name: web
|
|
||||||
description: A Helm chart for Kubernetes
|
|
||||||
type: application
|
|
||||||
version: 0.0.0
|
|
||||||
appVersion: "0.7.0-canary.18"
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
1. Get the application URL by running these commands:
|
|
||||||
{{- if contains "NodePort" .Values.service.type }}
|
|
||||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "web.fullname" . }})
|
|
||||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
|
||||||
echo http://$NODE_IP:$NODE_PORT
|
|
||||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
|
||||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
|
||||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "web.fullname" . }}'
|
|
||||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "web.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
|
||||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
|
||||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
|
||||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "web.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
|
||||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
|
||||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
|
||||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
{{/*
|
|
||||||
Expand the name of the chart.
|
|
||||||
*/}}
|
|
||||||
{{- define "web.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 "web.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 "web.chart" -}}
|
|
||||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Common labels
|
|
||||||
*/}}
|
|
||||||
{{- define "web.labels" -}}
|
|
||||||
helm.sh/chart: {{ include "web.chart" . }}
|
|
||||||
{{ include "web.selectorLabels" . }}
|
|
||||||
{{- if .Chart.AppVersion }}
|
|
||||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
||||||
{{- end }}
|
|
||||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
||||||
monitoring: enabled
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Selector labels
|
|
||||||
*/}}
|
|
||||||
{{- define "web.selectorLabels" -}}
|
|
||||||
app.kubernetes.io/name: {{ include "web.name" . }}
|
|
||||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
|
||||||
Create the name of the service account to use
|
|
||||||
*/}}
|
|
||||||
{{- define "web.serviceAccountName" -}}
|
|
||||||
{{- if .Values.serviceAccount.create }}
|
|
||||||
{{- default (include "web.fullname" .) .Values.serviceAccount.name }}
|
|
||||||
{{- else }}
|
|
||||||
{{- default "default" .Values.serviceAccount.name }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "web.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "web.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
replicas: {{ .Values.replicaCount }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "web.selectorLabels" . | nindent 6 }}
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
{{- with .Values.podAnnotations }}
|
|
||||||
annotations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
labels:
|
|
||||||
{{- include "web.selectorLabels" . | nindent 8 }}
|
|
||||||
spec:
|
|
||||||
{{- with .Values.imagePullSecrets }}
|
|
||||||
imagePullSecrets:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
serviceAccountName: {{ include "web.serviceAccountName" . }}
|
|
||||||
containers:
|
|
||||||
- name: {{ .Chart.Name }}
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
env:
|
|
||||||
- name: AFFINE_ENV
|
|
||||||
value: "{{ .Release.Namespace }}"
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: {{ .Values.service.port }}
|
|
||||||
protocol: TCP
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: {{ .Values.probe.initialDelaySeconds }}
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
|
||||||
{{- with .Values.nodeSelector }}
|
|
||||||
nodeSelector:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.affinity }}
|
|
||||||
affinity:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.tolerations }}
|
|
||||||
tolerations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "web.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "web.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
type: {{ .Values.service.type }}
|
|
||||||
ports:
|
|
||||||
- port: {{ .Values.service.port }}
|
|
||||||
targetPort: http
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
selector:
|
|
||||||
{{- include "web.selectorLabels" . | nindent 4 }}
|
|
||||||
37
.github/helm/affine/charts/web/values.yaml
vendored
37
.github/helm/affine/charts/web/values.yaml
vendored
@@ -1,37 +0,0 @@
|
|||||||
replicaCount: 1
|
|
||||||
|
|
||||||
image:
|
|
||||||
repository: ghcr.io/toeverything/affine-front
|
|
||||||
pullPolicy: IfNotPresent
|
|
||||||
tag: ""
|
|
||||||
|
|
||||||
imagePullSecrets: []
|
|
||||||
nameOverride: ""
|
|
||||||
fullnameOverride: ""
|
|
||||||
|
|
||||||
serviceAccount:
|
|
||||||
create: true
|
|
||||||
annotations: {}
|
|
||||||
name: "affine-web"
|
|
||||||
|
|
||||||
podAnnotations: {}
|
|
||||||
|
|
||||||
podSecurityContext:
|
|
||||||
fsGroup: 2000
|
|
||||||
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpu: '500m'
|
|
||||||
memory: 2Gi
|
|
||||||
requests:
|
|
||||||
cpu: '500m'
|
|
||||||
memory: 2Gi
|
|
||||||
|
|
||||||
nodeSelector: {}
|
|
||||||
|
|
||||||
tolerations: []
|
|
||||||
|
|
||||||
affinity: {}
|
|
||||||
|
|
||||||
probe:
|
|
||||||
initialDelaySeconds: 1
|
|
||||||
12
.github/helm/affine/templates/ingress.yaml
vendored
12
.github/helm/affine/templates/ingress.yaml
vendored
@@ -44,9 +44,9 @@ spec:
|
|||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: affine-sync
|
name: {{ $.Values.front.services.sync.name }}
|
||||||
port:
|
port:
|
||||||
number: {{ $.Values.sync.service.port }}
|
number: {{ $.Values.front.services.sync.port }}
|
||||||
- path: /graphql
|
- path: /graphql
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
@@ -65,15 +65,15 @@ spec:
|
|||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: affine-renderer
|
name: {{ $.Values.front.services.renderer.name }}
|
||||||
port:
|
port:
|
||||||
number: {{ $.Values.renderer.service.port }}
|
number: {{ $.Values.front.services.renderer.port }}
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: affine-web
|
name: {{ $.Values.front.services.web.name }}
|
||||||
port:
|
port:
|
||||||
number: {{ $.Values.web.service.port }}
|
number: {{ $.Values.front.services.web.port }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
36
.github/helm/affine/values.yaml
vendored
36
.github/helm/affine/values.yaml
vendored
@@ -47,27 +47,27 @@ graphql:
|
|||||||
annotations:
|
annotations:
|
||||||
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
||||||
|
|
||||||
sync:
|
|
||||||
service:
|
|
||||||
type: ClusterIP
|
|
||||||
port: 3010
|
|
||||||
annotations:
|
|
||||||
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
|
||||||
|
|
||||||
renderer:
|
|
||||||
service:
|
|
||||||
type: ClusterIP
|
|
||||||
port: 3000
|
|
||||||
annotations:
|
|
||||||
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
|
||||||
|
|
||||||
doc:
|
doc:
|
||||||
service:
|
service:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
annotations:
|
annotations:
|
||||||
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
||||||
|
|
||||||
web:
|
front:
|
||||||
service:
|
services:
|
||||||
type: ClusterIP
|
sync:
|
||||||
port: 8080
|
name: affine-sync
|
||||||
|
type: ClusterIP
|
||||||
|
port: 3010
|
||||||
|
annotations:
|
||||||
|
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
||||||
|
renderer:
|
||||||
|
name: affine-renderer
|
||||||
|
type: ClusterIP
|
||||||
|
port: 3000
|
||||||
|
annotations:
|
||||||
|
cloud.google.com/backend-config: '{"default": "affine-api-backendconfig"}'
|
||||||
|
web:
|
||||||
|
name: affine-web
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8080
|
||||||
|
|||||||
13
.github/workflows/build-images.yml
vendored
13
.github/workflows/build-images.yml
vendored
@@ -263,18 +263,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
app-version: ${{ inputs.app-version }}
|
app-version: ${{ inputs.app-version }}
|
||||||
|
|
||||||
- name: Build front Dockerfile
|
- name: Build backend Dockerfile
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
pull: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
provenance: true
|
|
||||||
file: .github/deployment/front/Dockerfile
|
|
||||||
tags: ghcr.io/toeverything/affine-front:${{inputs.build-type}}-${{ inputs.git-short-hash }}
|
|
||||||
|
|
||||||
- name: Build graphql Dockerfile
|
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
|||||||
@@ -44,3 +44,12 @@ e2e('should init renderer service', async t => {
|
|||||||
const res = await app.GET('/info').expect(200);
|
const res = await app.GET('/info').expect(200);
|
||||||
t.is(res.body.flavor, 'renderer');
|
t.is(res.body.flavor, 'renderer');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
e2e('should init front service', async t => {
|
||||||
|
// @ts-expect-error override
|
||||||
|
globalThis.env.FLAVOR = 'front';
|
||||||
|
await using app = await createApp();
|
||||||
|
|
||||||
|
const res = await app.GET('/info').expect(200);
|
||||||
|
t.is(res.body.flavor, 'front');
|
||||||
|
});
|
||||||
|
|||||||
@@ -68,12 +68,14 @@ test('should read DEPLOYMENT_TYPE', t => {
|
|||||||
|
|
||||||
test('should read FLAVOR', t => {
|
test('should read FLAVOR', t => {
|
||||||
t.deepEqual(
|
t.deepEqual(
|
||||||
['allinone', 'graphql', 'sync', 'renderer', 'doc', 'script'].map(envVal => {
|
['allinone', 'graphql', 'sync', 'renderer', 'front', 'doc', 'script'].map(
|
||||||
process.env.SERVER_FLAVOR = envVal;
|
envVal => {
|
||||||
const env = new Env();
|
process.env.SERVER_FLAVOR = envVal;
|
||||||
return env.FLAVOR;
|
const env = new Env();
|
||||||
}),
|
return env.FLAVOR;
|
||||||
['allinone', 'graphql', 'sync', 'renderer', 'doc', 'script']
|
}
|
||||||
|
),
|
||||||
|
['allinone', 'graphql', 'sync', 'renderer', 'front', 'doc', 'script']
|
||||||
);
|
);
|
||||||
|
|
||||||
t.throws(
|
t.throws(
|
||||||
@@ -83,7 +85,7 @@ test('should read FLAVOR', t => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
'Invalid value "unknown" for environment variable SERVER_FLAVOR, expected one of ["allinone","graphql","sync","renderer","doc","script"]',
|
'Invalid value "unknown" for environment variable SERVER_FLAVOR, expected one of ["allinone","graphql","sync","renderer","front","doc","script"]',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -110,6 +112,7 @@ test('should tell flavors correctly', t => {
|
|||||||
graphql: true,
|
graphql: true,
|
||||||
sync: true,
|
sync: true,
|
||||||
renderer: true,
|
renderer: true,
|
||||||
|
front: false,
|
||||||
doc: true,
|
doc: true,
|
||||||
script: false,
|
script: false,
|
||||||
});
|
});
|
||||||
@@ -119,6 +122,17 @@ test('should tell flavors correctly', t => {
|
|||||||
graphql: true,
|
graphql: true,
|
||||||
sync: false,
|
sync: false,
|
||||||
renderer: false,
|
renderer: false,
|
||||||
|
front: false,
|
||||||
|
doc: false,
|
||||||
|
script: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
process.env.SERVER_FLAVOR = 'front';
|
||||||
|
t.deepEqual(new Env().flavors, {
|
||||||
|
graphql: false,
|
||||||
|
sync: false,
|
||||||
|
renderer: false,
|
||||||
|
front: true,
|
||||||
doc: false,
|
doc: false,
|
||||||
script: false,
|
script: false,
|
||||||
});
|
});
|
||||||
@@ -128,6 +142,7 @@ test('should tell flavors correctly', t => {
|
|||||||
graphql: false,
|
graphql: false,
|
||||||
sync: false,
|
sync: false,
|
||||||
renderer: false,
|
renderer: false,
|
||||||
|
front: false,
|
||||||
doc: false,
|
doc: false,
|
||||||
script: true,
|
script: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { PermissionModule } from './core/permission';
|
|||||||
import { QueueDashboardModule } from './core/queue-dashboard';
|
import { QueueDashboardModule } from './core/queue-dashboard';
|
||||||
import { QuotaModule } from './core/quota';
|
import { QuotaModule } from './core/quota';
|
||||||
import { SelfhostModule } from './core/selfhost';
|
import { SelfhostModule } from './core/selfhost';
|
||||||
|
import { StaticFileModule } from './core/static-files';
|
||||||
import { StorageModule } from './core/storage';
|
import { StorageModule } from './core/storage';
|
||||||
import { SyncModule } from './core/sync';
|
import { SyncModule } from './core/sync';
|
||||||
import { TelemetryModule } from './core/telemetry';
|
import { TelemetryModule } from './core/telemetry';
|
||||||
@@ -173,10 +174,14 @@ export function buildAppModule(env: Env) {
|
|||||||
NotificationModule,
|
NotificationModule,
|
||||||
MailModule
|
MailModule
|
||||||
)
|
)
|
||||||
// renderer server only
|
// renderer server and front server
|
||||||
.useIf(() => env.flavors.renderer, DocRendererModule)
|
.useIf(() => env.flavors.renderer || env.flavors.front, DocRendererModule)
|
||||||
// sync server only
|
// sync server and front server
|
||||||
.useIf(() => env.flavors.sync, SyncModule, TelemetryModule)
|
.useIf(
|
||||||
|
() => env.flavors.sync || env.flavors.front,
|
||||||
|
SyncModule,
|
||||||
|
TelemetryModule
|
||||||
|
)
|
||||||
// graphql server only
|
// graphql server only
|
||||||
.useIf(
|
.useIf(
|
||||||
() => env.flavors.graphql,
|
() => env.flavors.graphql,
|
||||||
@@ -199,8 +204,10 @@ export function buildAppModule(env: Env) {
|
|||||||
)
|
)
|
||||||
// doc service only
|
// doc service only
|
||||||
.useIf(() => env.flavors.doc, DocServiceModule)
|
.useIf(() => env.flavors.doc, DocServiceModule)
|
||||||
// self hosted server only
|
// worker for and self-hosted API only for self-host and local development only
|
||||||
.useIf(() => env.dev || env.selfhosted, WorkerModule, SelfhostModule)
|
.useIf(() => env.dev || env.selfhosted, WorkerModule, SelfhostModule)
|
||||||
|
// static frontend routes for front flavor
|
||||||
|
.useIf(() => env.flavors.front, StaticFileModule)
|
||||||
|
|
||||||
// gcloud
|
// gcloud
|
||||||
.useIf(() => env.gcp, GCloudModule);
|
.useIf(() => env.gcp, GCloudModule);
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ export class SetupMiddleware implements NestMiddleware {
|
|||||||
constructor(private readonly server: ServerService) {}
|
constructor(private readonly server: ServerService) {}
|
||||||
|
|
||||||
use = (req: Request, res: Response, next: (error?: Error | any) => void) => {
|
use = (req: Request, res: Response, next: (error?: Error | any) => void) => {
|
||||||
|
if (!env.selfhosted) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// never throw
|
// never throw
|
||||||
this.server
|
this.server
|
||||||
.initialized()
|
.initialized()
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
|
||||||
|
import test from 'ava';
|
||||||
|
import express from 'express';
|
||||||
|
import request from 'supertest';
|
||||||
|
|
||||||
|
import { Namespace } from '../../../env';
|
||||||
|
import { StaticFilesResolver } from '../static';
|
||||||
|
|
||||||
|
const mobileUA =
|
||||||
|
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36';
|
||||||
|
|
||||||
|
function initStaticFixture(root: string) {
|
||||||
|
const staticRoot = join(root, 'static');
|
||||||
|
|
||||||
|
const files: Array<[string, string]> = [
|
||||||
|
['index.html', 'web-index'],
|
||||||
|
['admin/index.html', 'admin-index'],
|
||||||
|
['assets/main.js', 'web-asset'],
|
||||||
|
['mobile/index.html', 'mobile-index'],
|
||||||
|
['mobile/assets/main.js', 'mobile-asset'],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [file, content] of files) {
|
||||||
|
const fullPath = join(staticRoot, file);
|
||||||
|
mkdirSync(dirname(fullPath), { recursive: true });
|
||||||
|
writeFileSync(fullPath, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createApp(basePath = '') {
|
||||||
|
const app = express();
|
||||||
|
const resolver = new StaticFilesResolver(
|
||||||
|
{ server: { path: basePath } } as any,
|
||||||
|
{
|
||||||
|
httpAdapter: {
|
||||||
|
getInstance: () => app,
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
);
|
||||||
|
resolver.onModuleInit();
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
test.serial('serves admin files and admin route fallback', async t => {
|
||||||
|
const fixtureRoot = mkdtempSync(join(tmpdir(), 'affine-static-files-'));
|
||||||
|
initStaticFixture(fixtureRoot);
|
||||||
|
|
||||||
|
const prevProjectRoot = env.projectRoot;
|
||||||
|
const prevNamespace = env.NAMESPACE;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = fixtureRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = Namespace.Production;
|
||||||
|
|
||||||
|
const app = await createApp();
|
||||||
|
|
||||||
|
const indexRes = await request(app).get('/admin/index.html').expect(200);
|
||||||
|
t.is(indexRes.text, 'admin-index');
|
||||||
|
|
||||||
|
const fallbackRes = await request(app).get('/admin/settings').expect(200);
|
||||||
|
t.is(fallbackRes.text, 'admin-index');
|
||||||
|
} finally {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = prevProjectRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = prevNamespace;
|
||||||
|
rmSync(fixtureRoot, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial(
|
||||||
|
'serves static assets from prefixed paths and returns 404 on missing',
|
||||||
|
async t => {
|
||||||
|
const fixtureRoot = mkdtempSync(join(tmpdir(), 'affine-static-files-'));
|
||||||
|
initStaticFixture(fixtureRoot);
|
||||||
|
|
||||||
|
const prevProjectRoot = env.projectRoot;
|
||||||
|
const prevNamespace = env.NAMESPACE;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = fixtureRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = Namespace.Production;
|
||||||
|
|
||||||
|
const app = await createApp();
|
||||||
|
|
||||||
|
const assetRes = await request(app).get('/assets/main.js').expect(200);
|
||||||
|
t.is(assetRes.text, 'web-asset');
|
||||||
|
|
||||||
|
await request(app).get('/assets/missing.js').expect(404);
|
||||||
|
} finally {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = prevProjectRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = prevNamespace;
|
||||||
|
rmSync(fixtureRoot, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.serial(
|
||||||
|
'matches front container index behavior and cache header',
|
||||||
|
async t => {
|
||||||
|
const fixtureRoot = mkdtempSync(join(tmpdir(), 'affine-static-files-'));
|
||||||
|
initStaticFixture(fixtureRoot);
|
||||||
|
|
||||||
|
const prevProjectRoot = env.projectRoot;
|
||||||
|
const prevNamespace = env.NAMESPACE;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = fixtureRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = Namespace.Production;
|
||||||
|
|
||||||
|
const app = await createApp();
|
||||||
|
|
||||||
|
const indexRes = await request(app).get('/index.html').expect(200);
|
||||||
|
t.is(indexRes.text, 'web-index');
|
||||||
|
t.is(
|
||||||
|
indexRes.headers['cache-control'],
|
||||||
|
'private, no-cache, no-store, max-age=0, must-revalidate'
|
||||||
|
);
|
||||||
|
|
||||||
|
const fallbackRes = await request(app).get('/workspace/path').expect(200);
|
||||||
|
t.is(fallbackRes.text, 'web-index');
|
||||||
|
} finally {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = prevProjectRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = prevNamespace;
|
||||||
|
rmSync(fixtureRoot, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.serial('uses mobile root only in dev namespace for mobile UA', async t => {
|
||||||
|
const fixtureRoot = mkdtempSync(join(tmpdir(), 'affine-static-files-'));
|
||||||
|
initStaticFixture(fixtureRoot);
|
||||||
|
|
||||||
|
const prevProjectRoot = env.projectRoot;
|
||||||
|
const prevNamespace = env.NAMESPACE;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = fixtureRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = Namespace.Dev;
|
||||||
|
|
||||||
|
const app = await createApp();
|
||||||
|
|
||||||
|
const mobileAssetRes = await request(app)
|
||||||
|
.get('/assets/main.js')
|
||||||
|
.set('user-agent', mobileUA)
|
||||||
|
.expect(200);
|
||||||
|
t.is(mobileAssetRes.text, 'mobile-asset');
|
||||||
|
|
||||||
|
const webAssetRes = await request(app).get('/assets/main.js').expect(200);
|
||||||
|
t.is(webAssetRes.text, 'web-asset');
|
||||||
|
|
||||||
|
const mobileFromHint = await request(app)
|
||||||
|
.get('/assets/main.js')
|
||||||
|
.set('user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
|
||||||
|
.set('sec-ch-ua-mobile', '?1')
|
||||||
|
.expect(200);
|
||||||
|
t.is(mobileFromHint.text, 'mobile-asset');
|
||||||
|
|
||||||
|
const desktopFromHint = await request(app)
|
||||||
|
.get('/assets/main.js')
|
||||||
|
.set('user-agent', mobileUA)
|
||||||
|
.set('sec-ch-ua-mobile', '?0')
|
||||||
|
.expect(200);
|
||||||
|
t.is(desktopFromHint.text, 'web-asset');
|
||||||
|
|
||||||
|
const mobileFromPlatformHint = await request(app)
|
||||||
|
.get('/assets/main.js')
|
||||||
|
.set('sec-ch-ua-platform', '"Android"')
|
||||||
|
.expect(200);
|
||||||
|
t.is(mobileFromPlatformHint.text, 'mobile-asset');
|
||||||
|
} finally {
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.projectRoot = prevProjectRoot;
|
||||||
|
// @ts-expect-error test override
|
||||||
|
env.NAMESPACE = prevNamespace;
|
||||||
|
rmSync(fixtureRoot, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
8
packages/backend/server/src/core/static-files/index.ts
Normal file
8
packages/backend/server/src/core/static-files/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { StaticFilesResolver } from './static';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [StaticFilesResolver],
|
||||||
|
})
|
||||||
|
export class StaticFileModule {}
|
||||||
119
packages/backend/server/src/core/static-files/static.ts
Normal file
119
packages/backend/server/src/core/static-files/static.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { HttpAdapterHost } from '@nestjs/core';
|
||||||
|
import type { Application, Request, Response } from 'express';
|
||||||
|
import { static as serveStatic } from 'express';
|
||||||
|
|
||||||
|
import { Config } from '../../base';
|
||||||
|
import { isMobileRequest } from '../utils/user-agent';
|
||||||
|
|
||||||
|
const staticPathRegex = /^\/(_plugin|assets|imgs|js|plugins|static)\//;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StaticFilesResolver implements OnModuleInit {
|
||||||
|
constructor(
|
||||||
|
private readonly config: Config,
|
||||||
|
private readonly adapterHost: HttpAdapterHost
|
||||||
|
) {}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
if (!this.adapterHost.httpAdapter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = this.adapterHost.httpAdapter.getInstance<Application>();
|
||||||
|
const basePath = this.config.server.path;
|
||||||
|
const rootPath = basePath || '/';
|
||||||
|
const staticPath = join(env.projectRoot, 'static');
|
||||||
|
const adminPath = join(staticPath, 'admin');
|
||||||
|
const mobilePath = env.namespaces.canary
|
||||||
|
? join(staticPath, 'mobile')
|
||||||
|
: staticPath;
|
||||||
|
|
||||||
|
const staticAsset = serveStatic(staticPath, {
|
||||||
|
redirect: false,
|
||||||
|
index: false,
|
||||||
|
fallthrough: true,
|
||||||
|
});
|
||||||
|
const mobileAsset = serveStatic(mobilePath, {
|
||||||
|
redirect: false,
|
||||||
|
index: false,
|
||||||
|
fallthrough: true,
|
||||||
|
});
|
||||||
|
const staticAssetStrict = serveStatic(staticPath, {
|
||||||
|
redirect: false,
|
||||||
|
index: false,
|
||||||
|
fallthrough: false,
|
||||||
|
});
|
||||||
|
const mobileAssetStrict = serveStatic(mobilePath, {
|
||||||
|
redirect: false,
|
||||||
|
index: false,
|
||||||
|
fallthrough: false,
|
||||||
|
});
|
||||||
|
const adminAsset = serveStatic(adminPath, {
|
||||||
|
redirect: false,
|
||||||
|
index: false,
|
||||||
|
fallthrough: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const routeByUA = (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: (err?: unknown) => void,
|
||||||
|
strict = false
|
||||||
|
) => {
|
||||||
|
const isMobile = isMobileRequest(req.headers);
|
||||||
|
if (strict) {
|
||||||
|
return isMobile
|
||||||
|
? mobileAssetStrict(req, res, next)
|
||||||
|
: staticAssetStrict(req, res, next);
|
||||||
|
}
|
||||||
|
return isMobile
|
||||||
|
? mobileAsset(req, res, next)
|
||||||
|
: staticAsset(req, res, next);
|
||||||
|
};
|
||||||
|
|
||||||
|
// /admin
|
||||||
|
app.use(basePath + '/admin', adminAsset);
|
||||||
|
app.get([basePath + '/admin', basePath + '/admin/*path'], (_req, res) => {
|
||||||
|
res.sendFile(join(adminPath, 'index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// /_plugin|/assets|/imgs|/js|/plugins|/static
|
||||||
|
app.use(rootPath, (req, res, next) => {
|
||||||
|
if (!staticPathRegex.test(req.path)) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
routeByUA(req, res, next, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// /
|
||||||
|
app.use(rootPath, (req, res, next) => {
|
||||||
|
if (req.path.startsWith('/admin')) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader(
|
||||||
|
'Cache-Control',
|
||||||
|
'private, no-cache, no-store, max-age=0, must-revalidate'
|
||||||
|
);
|
||||||
|
routeByUA(req, res, next, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
[basePath || '/', basePath + '/*path'],
|
||||||
|
(req: Request, res: Response) => {
|
||||||
|
if (req.path.startsWith('/admin')) {
|
||||||
|
res.status(404).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = isMobileRequest(req.headers) ? mobilePath : staticPath;
|
||||||
|
res.sendFile(join(root, 'index.html'));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isMobileRequest,
|
||||||
|
isMobileUserAgent,
|
||||||
|
parseRequestUserAgent,
|
||||||
|
parseUserAgent,
|
||||||
|
} from '../user-agent';
|
||||||
|
|
||||||
|
const mobileUserAgent =
|
||||||
|
'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36';
|
||||||
|
const desktopUserAgent =
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
||||||
|
|
||||||
|
test('returns desktop for empty user agent values', t => {
|
||||||
|
t.false(isMobileUserAgent(undefined));
|
||||||
|
|
||||||
|
t.deepEqual(parseUserAgent(undefined), {
|
||||||
|
ua: '',
|
||||||
|
deviceType: 'desktop',
|
||||||
|
isMobile: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detects mobile and desktop user agents', t => {
|
||||||
|
const mobile = parseUserAgent(mobileUserAgent);
|
||||||
|
t.true(mobile.isMobile);
|
||||||
|
t.is(mobile.deviceType, 'mobile');
|
||||||
|
|
||||||
|
const desktop = parseUserAgent(desktopUserAgent);
|
||||||
|
t.false(desktop.isMobile);
|
||||||
|
t.is(desktop.deviceType, 'desktop');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prefers sec-ch-ua-mobile over user-agent when available', t => {
|
||||||
|
const mobileFromHint = parseRequestUserAgent({
|
||||||
|
'user-agent': desktopUserAgent,
|
||||||
|
'sec-ch-ua-mobile': '?1',
|
||||||
|
});
|
||||||
|
t.true(mobileFromHint.isMobile);
|
||||||
|
t.is(mobileFromHint.deviceType, 'mobile');
|
||||||
|
t.true(isMobileRequest({ 'sec-ch-ua-mobile': '?1' }));
|
||||||
|
|
||||||
|
const desktopFromHint = parseRequestUserAgent({
|
||||||
|
'user-agent': mobileUserAgent,
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
});
|
||||||
|
t.false(desktopFromHint.isMobile);
|
||||||
|
t.is(desktopFromHint.deviceType, 'desktop');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('uses sec-ch-ua-platform as fallback hint', t => {
|
||||||
|
const parsed = parseRequestUserAgent({
|
||||||
|
'sec-ch-ua-platform': '"Android"',
|
||||||
|
});
|
||||||
|
t.true(parsed.isMobile);
|
||||||
|
t.is(parsed.deviceType, 'mobile');
|
||||||
|
|
||||||
|
const desktop = parseRequestUserAgent({
|
||||||
|
'sec-ch-ua-platform': '"Windows"',
|
||||||
|
});
|
||||||
|
t.false(desktop.isMobile);
|
||||||
|
t.is(desktop.deviceType, 'desktop');
|
||||||
|
t.false(isMobileUserAgent(undefined));
|
||||||
|
});
|
||||||
207
packages/backend/server/src/core/utils/user-agent.ts
Normal file
207
packages/backend/server/src/core/utils/user-agent.ts
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import type { IncomingHttpHeaders } from 'node:http';
|
||||||
|
|
||||||
|
import isMobile from 'is-mobile';
|
||||||
|
|
||||||
|
export type UserAgentHeader = IncomingHttpHeaders['user-agent'];
|
||||||
|
|
||||||
|
export type UserAgentDeviceType = 'desktop' | 'mobile';
|
||||||
|
|
||||||
|
export interface ParsedUserAgent {
|
||||||
|
readonly ua: string;
|
||||||
|
readonly deviceType: UserAgentDeviceType;
|
||||||
|
readonly isMobile: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeaderValue = string | string[] | undefined;
|
||||||
|
|
||||||
|
const USER_AGENT_MAX_LENGTH = 512;
|
||||||
|
const USER_AGENT_CACHE_MAX_SIZE = 1024;
|
||||||
|
|
||||||
|
const CLIENT_HINT_MOBILE_TRUE = new Set(['?1', '1', 'true']);
|
||||||
|
const MOBILE_PLATFORM_HINTS = new Set(['android', 'ios', 'ipados']);
|
||||||
|
|
||||||
|
const EMPTY_USER_AGENT: ParsedUserAgent = {
|
||||||
|
ua: '',
|
||||||
|
deviceType: 'desktop',
|
||||||
|
isMobile: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsedUserAgentCache = new Map<string, ParsedUserAgent>();
|
||||||
|
|
||||||
|
interface UserAgentSignals {
|
||||||
|
ua: string;
|
||||||
|
clientHintMobile?: boolean;
|
||||||
|
clientHintPlatform?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickHeaderValue(value: HeaderValue): string {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.find(item => typeof item === 'string' && item.length > 0) ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHeaderValue(value: HeaderValue): string {
|
||||||
|
const header = pickHeaderValue(value);
|
||||||
|
if (!header) return '';
|
||||||
|
return header.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unquoteHeaderValue(value: string): string {
|
||||||
|
if (!value) return value;
|
||||||
|
|
||||||
|
if (value.startsWith('"') && value.endsWith('"')) {
|
||||||
|
return value.slice(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUserAgentHeader(value: UserAgentHeader): string {
|
||||||
|
const normalized = normalizeHeaderValue(value);
|
||||||
|
if (!normalized) return '';
|
||||||
|
if (normalized.length <= USER_AGENT_MAX_LENGTH) return normalized;
|
||||||
|
return normalized.slice(0, USER_AGENT_MAX_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseClientHintMobile(value: HeaderValue): boolean | undefined {
|
||||||
|
const normalized = unquoteHeaderValue(normalizeHeaderValue(value));
|
||||||
|
if (!normalized) return;
|
||||||
|
if (CLIENT_HINT_MOBILE_TRUE.has(normalized)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseClientHintPlatform(value: HeaderValue): string | undefined {
|
||||||
|
const normalized = unquoteHeaderValue(normalizeHeaderValue(value));
|
||||||
|
if (!normalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseUserAgentSignals(headers: IncomingHttpHeaders): UserAgentSignals {
|
||||||
|
return {
|
||||||
|
ua: normalizeUserAgentHeader(headers['user-agent']),
|
||||||
|
clientHintMobile: parseClientHintMobile(headers['sec-ch-ua-mobile']),
|
||||||
|
clientHintPlatform: parseClientHintPlatform(headers['sec-ch-ua-platform']),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeviceType(signals: UserAgentSignals): UserAgentDeviceType {
|
||||||
|
if (signals.clientHintMobile !== undefined) {
|
||||||
|
return signals.clientHintMobile ? 'mobile' : 'desktop';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
signals.clientHintPlatform &&
|
||||||
|
MOBILE_PLATFORM_HINTS.has(signals.clientHintPlatform)
|
||||||
|
) {
|
||||||
|
return 'mobile';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signals.ua) {
|
||||||
|
return 'desktop';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mobile = isMobile({
|
||||||
|
ua: signals.ua,
|
||||||
|
tablet: true,
|
||||||
|
featureDetect: false,
|
||||||
|
});
|
||||||
|
return mobile ? 'mobile' : 'desktop';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCacheKey(signals: UserAgentSignals): string {
|
||||||
|
const hintMobile =
|
||||||
|
signals.clientHintMobile === undefined
|
||||||
|
? ''
|
||||||
|
: signals.clientHintMobile
|
||||||
|
? '1'
|
||||||
|
: '0';
|
||||||
|
const hintPlatform = signals.clientHintPlatform ?? '';
|
||||||
|
return `${signals.ua}|${hintMobile}|${hintPlatform}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedParsedUserAgent(
|
||||||
|
cacheKey: string
|
||||||
|
): ParsedUserAgent | undefined {
|
||||||
|
const cached = parsedUserAgentCache.get(cacheKey);
|
||||||
|
if (!cached) return;
|
||||||
|
|
||||||
|
// Keep recently-used entries hot in the bounded cache.
|
||||||
|
parsedUserAgentCache.delete(cacheKey);
|
||||||
|
parsedUserAgentCache.set(cacheKey, cached);
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheParsedUserAgent(
|
||||||
|
cacheKey: string,
|
||||||
|
parsedUserAgent: ParsedUserAgent
|
||||||
|
) {
|
||||||
|
if (parsedUserAgentCache.has(cacheKey)) {
|
||||||
|
parsedUserAgentCache.delete(cacheKey);
|
||||||
|
} else if (parsedUserAgentCache.size >= USER_AGENT_CACHE_MAX_SIZE) {
|
||||||
|
const oldestUserAgent = parsedUserAgentCache.keys().next();
|
||||||
|
if (!oldestUserAgent.done) {
|
||||||
|
parsedUserAgentCache.delete(oldestUserAgent.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedUserAgentCache.set(cacheKey, parsedUserAgent);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseUserAgentWithSignals(signals: UserAgentSignals): ParsedUserAgent {
|
||||||
|
const cacheKey = getCacheKey(signals);
|
||||||
|
const cached = getCachedParsedUserAgent(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const deviceType = getDeviceType(signals);
|
||||||
|
const parsed: ParsedUserAgent = {
|
||||||
|
ua: signals.ua,
|
||||||
|
deviceType,
|
||||||
|
isMobile: deviceType === 'mobile',
|
||||||
|
};
|
||||||
|
|
||||||
|
cacheParsedUserAgent(cacheKey, parsed);
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseUserAgent(
|
||||||
|
userAgentHeader: UserAgentHeader
|
||||||
|
): ParsedUserAgent {
|
||||||
|
const ua = normalizeUserAgentHeader(userAgentHeader);
|
||||||
|
if (!ua) {
|
||||||
|
return EMPTY_USER_AGENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseUserAgentWithSignals({ ua });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMobileUserAgent(userAgentHeader: UserAgentHeader): boolean {
|
||||||
|
return parseUserAgent(userAgentHeader).isMobile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRequestUserAgent(
|
||||||
|
headers: IncomingHttpHeaders
|
||||||
|
): ParsedUserAgent {
|
||||||
|
const signals = parseUserAgentSignals(headers);
|
||||||
|
if (
|
||||||
|
!signals.ua &&
|
||||||
|
signals.clientHintMobile === undefined &&
|
||||||
|
!signals.clientHintPlatform
|
||||||
|
) {
|
||||||
|
return EMPTY_USER_AGENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseUserAgentWithSignals(signals);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMobileRequest(headers: IncomingHttpHeaders): boolean {
|
||||||
|
return parseRequestUserAgent(headers).isMobile;
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ export enum Flavor {
|
|||||||
Graphql = 'graphql',
|
Graphql = 'graphql',
|
||||||
Sync = 'sync',
|
Sync = 'sync',
|
||||||
Renderer = 'renderer',
|
Renderer = 'renderer',
|
||||||
|
Front = 'front',
|
||||||
Doc = 'doc',
|
Doc = 'doc',
|
||||||
Script = 'script',
|
Script = 'script',
|
||||||
}
|
}
|
||||||
@@ -108,6 +109,7 @@ export class Env implements AppEnv {
|
|||||||
graphql: this.isFlavor(Flavor.Graphql),
|
graphql: this.isFlavor(Flavor.Graphql),
|
||||||
sync: this.isFlavor(Flavor.Sync),
|
sync: this.isFlavor(Flavor.Sync),
|
||||||
renderer: this.isFlavor(Flavor.Renderer),
|
renderer: this.isFlavor(Flavor.Renderer),
|
||||||
|
front: this.FLAVOR === Flavor.Front,
|
||||||
doc: this.isFlavor(Flavor.Doc),
|
doc: this.isFlavor(Flavor.Doc),
|
||||||
// Script in a special flavor, return true only when it is set explicitly
|
// Script in a special flavor, return true only when it is set explicitly
|
||||||
script: this.FLAVOR === Flavor.Script,
|
script: this.FLAVOR === Flavor.Script,
|
||||||
|
|||||||
@@ -103,8 +103,7 @@ ios_new_version=${IOS_APP_VERSION:-$new_version}
|
|||||||
|
|
||||||
update_app_version_in_helm_charts ".github/helm/affine/Chart.yaml" "$new_version"
|
update_app_version_in_helm_charts ".github/helm/affine/Chart.yaml" "$new_version"
|
||||||
update_app_version_in_helm_charts ".github/helm/affine/charts/graphql/Chart.yaml" "$new_version"
|
update_app_version_in_helm_charts ".github/helm/affine/charts/graphql/Chart.yaml" "$new_version"
|
||||||
update_app_version_in_helm_charts ".github/helm/affine/charts/sync/Chart.yaml" "$new_version"
|
update_app_version_in_helm_charts ".github/helm/affine/charts/front/Chart.yaml" "$new_version"
|
||||||
update_app_version_in_helm_charts ".github/helm/affine/charts/renderer/Chart.yaml" "$new_version"
|
|
||||||
update_app_version_in_helm_charts ".github/helm/affine/charts/doc/Chart.yaml" "$new_version"
|
update_app_version_in_helm_charts ".github/helm/affine/charts/doc/Chart.yaml" "$new_version"
|
||||||
|
|
||||||
update_app_stream_version "packages/frontend/apps/electron/resources/affine.metainfo.xml" "$new_version"
|
update_app_stream_version "packages/frontend/apps/electron/resources/affine.metainfo.xml" "$new_version"
|
||||||
|
|||||||
Reference in New Issue
Block a user