Skip to content
This repository was archived by the owner on Aug 21, 2024. It is now read-only.

Commit 0108def

Browse files
authored
Allows for serving client from API pods. (#7351)
Added configurable ability to build and serve client files from API pods instead of standalone client pods. Added a new API Dockerfile that builds the API and client portions in separate stages, then copies just the built client files into the final image. build_and_publish_package.sh now takes an additional parameter for the Dockerfile name, so that there can be multiple Dockerfiles for the same service. In this case, the api server can be built plainly with Dockerfile-api, or with the client included with Dockerfile-api-client.
1 parent c8374ab commit 0108def

13 files changed

Lines changed: 233 additions & 35 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
ARG ECR_URL
2+
ARG REPO_NAME
3+
ARG STAGE
4+
FROM ${ECR_URL}/${REPO_NAME}-root:latest_${STAGE} as apiBuilder
5+
6+
# Create app directory
7+
WORKDIR /app
8+
9+
# to make use of caching, copy only package files and install dependencies
10+
COPY packages/server/package.json ./packages/server/
11+
12+
ARG NODE_ENV
13+
RUN npm install --loglevel notice --legacy-peer-deps
14+
15+
COPY . .
16+
17+
# copy then compile the code
18+
19+
ENV APP_ENV=production
20+
21+
FROM ${ECR_URL}/${REPO_NAME}-root:latest_${STAGE} as clientBuilder
22+
23+
# Create app directory
24+
WORKDIR /app
25+
26+
COPY package.json .
27+
COPY packages/client/package.json ./packages/client/
28+
COPY packages/client-core/package.json ./packages/client-core/
29+
COPY packages/editor/package.json ./packages/editor/
30+
COPY packages/hyperflux/package.json ./packages/hyperflux/
31+
COPY packages/ui/package.json ./packages/ui/
32+
COPY patches/ ./patches/
33+
34+
ARG NODE_ENV
35+
RUN npm install --loglevel notice --legacy-peer-deps
36+
37+
# copy then compile the code
38+
COPY . .
39+
40+
ARG STORAGE_PROVIDER
41+
ARG STORAGE_CLOUDFRONT_DOMAIN
42+
ARG STORAGE_S3_STATIC_RESOURCE_BUCKET
43+
ARG STORAGE_AWS_ACCESS_KEY_ID
44+
ARG STORAGE_AWS_ACCESS_KEY_SECRET
45+
ARG STORAGE_S3_REGION
46+
ARG STORAGE_S3_AVATAR_DIRECTORY
47+
ARG SERVE_CLIENT_FROM_STORAGE_PROVIDER
48+
ARG MYSQL_HOST
49+
ARG MYSQL_USER
50+
ARG MYSQL_PORT
51+
ARG MYSQL_PASSWORD
52+
ARG MYSQL_DATABASE
53+
ARG VITE_APP_HOST
54+
ARG VITE_APP_PORT
55+
ARG VITE_SERVER_HOST
56+
ARG VITE_SERVER_PORT
57+
ARG VITE_FILE_SERVER
58+
ARG VITE_MEDIATOR_SERVER
59+
ARG VITE_LOGIN_WITH_WALLET
60+
ARG VITE_8TH_WALL
61+
ARG VITE_INSTANCESERVER_HOST
62+
ARG VITE_INSTANCESERVER_PORT
63+
ARG VITE_LOCAL_BUILD
64+
ARG VITE_READY_PLAYER_ME_URL
65+
ARG VITE_DISABLE_LOG
66+
ENV MYSQL_HOST=$MYSQL_HOST
67+
ENV MYSQL_PORT=$MYSQL_PORT
68+
ENV MYSQL_PASSWORD=$MYSQL_PASSWORD
69+
ENV MYSQL_DATABASE=$MYSQL_DATABASE
70+
ENV MYSQL_USER=$MYSQL_USER
71+
ENV VITE_APP_HOST=$VITE_APP_HOST
72+
ENV VITE_APP_PORT=$VITE_APP_PORT
73+
ENV VITE_SERVER_HOST=$VITE_SERVER_HOST
74+
ENV VITE_FILE_SERVER=$VITE_FILE_SERVER
75+
ENV VITE_SERVER_PORT=$VITE_SERVER_PORT
76+
ENV VITE_MEDIATOR_SERVER=$VITE_MEDIATOR_SERVER
77+
ENV VITE_LOGIN_WITH_WALLET=$VITE_LOGIN_WITH_WALLET
78+
ENV VITE_8TH_WALL=$VITE_8TH_WALL
79+
ENV VITE_INSTANCESERVER_HOST=$VITE_INSTANCESERVER_HOST
80+
ENV VITE_INSTANCESERVER_PORT=$VITE_INSTANCESERVER_PORT
81+
ENV VITE_LOCAL_BUILD=$VITE_LOCAL_BUILD
82+
ENV VITE_READY_PLAYER_ME_URL=$VITE_READY_PLAYER_ME_URL
83+
ENV VITE_DISABLE_LOG=$VITE_DISABLE_LOG
84+
85+
RUN npm run build-client
86+
87+
RUN if [ "$SERVE_CLIENT_FROM_STORAGE_PROVIDER" = "true" ] && [ "$STORAGE_PROVIDER" = "aws" ] ; then npm run push-client-dist-to-s3 ; fi
88+
89+
RUN rm -r packages/client/public
90+
91+
ENV APP_ENV=production
92+
93+
FROM node:18-buster-slim as runner
94+
RUN apt update
95+
RUN apt-get -y install git
96+
RUN apt-get -y install git-lfs
97+
WORKDIR /app
98+
99+
COPY --from=apiBuilder /app ./
100+
COPY --from=clientBuilder /app/packages/client ./packages/client
101+
102+
CMD ["scripts/start-server.sh"]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
aws/
2+
awscliv2.zip
3+
.git/
4+
*docker-compose*
5+
node_modules
6+
kubectl
7+
lib/
8+
packages/native-plugin-example
9+
packages/ops
10+
readme/
11+
tests/
12+
vendor/
13+
packages/**/node_modules
14+
package-lock.json
15+
packages/**/package-lock.json
16+
packages/bot
17+
packages/instanceserver
18+
packages/taskserver
19+
packages/test
20+
21+
22+
kubernetes/**
23+
.vscode
24+
config/prod.json
25+
26+
.env
27+
.env.local
28+
.idea/

dockerfiles/api/Dockerfile-api.dockerignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ packages/client
1919
packages/client-core
2020
packages/editor
2121
packages/instanceserver
22-
packages/ops
2322
packages/taskserver
2423
packages/test
2524
packages/ui

packages/client/server.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ app.use(serve(join(packageRoot, 'packages', 'client', 'dist'), {
2020
}));
2121

2222
app.use(async (ctx) => {
23-
await sendFile(ctx, join('dist', 'index.html'));
23+
await sendFile(ctx, join('dist', 'index.html'), {
24+
root: join(packageRoot, 'packages', 'client')
25+
});
2426
});
2527

2628
app.listen = function () {
2729
let server;
2830
if (HTTPS) {
29-
const key = readFileSync('../../certs/key.pem');
30-
const cert = readFileSync('../../certs/cert.pem');
31+
const key = readFileSync(join(packageRoot, 'certs/key.pem'))
32+
const cert = readFileSync(join(packageRoot, 'certs/cert.pem'))
3133
server = createServer({key: key, cert: cert }, this.callback());
3234
} else {
3335
server = _createServer(this.callback());

packages/client/vite.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
2-
import appRootPath from 'app-root-path'
2+
import packageRoot from 'app-root-path'
33
import dotenv from 'dotenv'
44
import fs from 'fs'
55
import fsExtra from 'fs-extra'
@@ -122,7 +122,7 @@ copyProjectDependencies()
122122

123123
export default defineConfig(async () => {
124124
dotenv.config({
125-
path: appRootPath.path + '/.env.local'
125+
path: packageRoot.path + '/.env.local'
126126
})
127127
const clientSetting = await getClientSetting()
128128

@@ -151,8 +151,8 @@ export default defineConfig(async () => {
151151
...(process.env.APP_ENV === 'development' || process.env.VITE_LOCAL_BUILD === 'true'
152152
? {
153153
https: {
154-
key: fs.readFileSync('../../certs/key.pem'),
155-
cert: fs.readFileSync('../../certs/cert.pem')
154+
key: fs.readFileSync(path.join(packageRoot.path, 'certs/key.pem')),
155+
cert: fs.readFileSync(path.join(packageRoot.path, 'certs/cert.pem'))
156156
}
157157
}
158158
: {})

packages/instanceserver/vite.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
6+
import packageRoot from 'app-root-path'
67
import fs from 'fs'
78
import path from 'path'
89
import url from 'url'
@@ -42,8 +43,8 @@ export default defineConfig(
4243
hmr: false,
4344
port: process.env.INSTANCESERVER_PORT ? parseInt(process.env.INSTANCESERVER_PORT) : undefined,
4445
https: {
45-
key: fs.readFileSync('../../certs/key.pem'),
46-
cert: fs.readFileSync('../../certs/cert.pem')
46+
key: fs.readFileSync(path.join(packageRoot.path, 'certs/key.pem')),
47+
cert: fs.readFileSync(path.join(packageRoot.path, 'certs/cert.pem'))
4748
}
4849
},
4950
resolve: {

packages/server-core/src/appconfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ const client = {
155155
(process.env.VITE_LOCAL_BUILD
156156
? 'http://' + process.env.APP_HOST + ':' + process.env.APP_PORT
157157
: 'https://' + process.env.APP_HOST + ':' + process.env.APP_PORT),
158+
port: process.env.APP_PORT || '3000',
158159
releaseName: process.env.RELEASE_NAME || 'local'
159160
}
160161

packages/server-core/src/createFileServer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import packageRoot from 'app-root-path'
12
import fs from 'fs'
23
import https from 'https'
34
import net from 'net'
5+
import { join } from 'path'
46
import serveStatic from 'serve-static'
57

68
import config from './appconfig'
@@ -9,8 +11,8 @@ const serve = serveStatic('../server/upload/')
911

1012
let server: https.Server = null!
1113
const options = {
12-
key: fs.readFileSync('../../certs/key.pem'),
13-
cert: fs.readFileSync('../../certs/cert.pem')
14+
key: fs.readFileSync(join(packageRoot.path, 'certs/key.pem')),
15+
cert: fs.readFileSync(join(packageRoot.path, 'certs/cert.pem'))
1416
}
1517

1618
const createTestFileServer = (port: number, isServerRunning: boolean) => {

packages/server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
"cross-env": "7.0.3",
6060
"feathers-sync": "3.0.2",
6161
"koa-favicon": "^2.1.0",
62+
"koa-send": "5.0.1",
63+
"koa-static": "5.0.0",
6264
"nanoid": "3.3.4",
6365
"ps-list": "7.2.0",
6466
"sequelize": "6.29.3",

packages/server/src/start.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import { koa } from '@feathersjs/koa'
2+
import packageRoot from 'app-root-path'
13
import fs from 'fs'
24
import https from 'https'
35
import favicon from 'koa-favicon'
4-
import path from 'path'
6+
import sendFile from 'koa-send'
7+
import serve from 'koa-static'
8+
import { join } from 'path'
59
import psList from 'ps-list'
610

711
import config from '@etherealengine/server-core/src/appconfig'
@@ -21,7 +25,7 @@ process.on('unhandledRejection', (error, promise) => {
2125
export const start = async (): Promise<void> => {
2226
const app = createFeathersKoaApp(ServerMode.API)
2327

24-
app.use(favicon(path.join(config.server.publicDir, 'favicon.ico')))
28+
app.use(favicon(join(config.server.publicDir, 'favicon.ico')))
2529
app.configure(channels)
2630

2731
if (!config.kubernetes.enabled && !config.db.forceRefresh && !config.testEnabled) {
@@ -100,4 +104,37 @@ export const start = async (): Promise<void> => {
100104
if (!config.kubernetes.enabled) {
101105
StartCorsServer(useSSL, certOptions)
102106
}
107+
108+
if (process.env.SERVE_CLIENT_FROM_API) {
109+
const clientApp = koa()
110+
clientApp.use(
111+
serve(join(packageRoot.path, 'packages', 'client', 'dist'), {
112+
brotli: true,
113+
setHeaders: (ctx) => {
114+
ctx.setHeader('Origin-Agent-Cluster', '?1')
115+
}
116+
})
117+
)
118+
clientApp.use(async (ctx) => {
119+
await sendFile(ctx, join('dist', 'index.html'), {
120+
root: join(packageRoot.path, 'packages', 'client')
121+
})
122+
})
123+
clientApp.listen = function () {
124+
let server
125+
const HTTPS = process.env.VITE_LOCAL_BUILD ?? false
126+
if (HTTPS) {
127+
const key = fs.readFileSync(join(packageRoot.path, 'certs/key.pem'))
128+
const cert = fs.readFileSync(join(packageRoot.path, 'certs/cert.pem'))
129+
server = https.createServer({ key: key, cert: cert }, this.callback())
130+
} else {
131+
const http = require('http')
132+
server = http.createServer(this.callback())
133+
}
134+
return server.listen.apply(server, arguments)
135+
}
136+
137+
const PORT = parseInt(config.client.port) || 3000
138+
clientApp.listen(PORT, () => console.log(`Client listening on port: ${PORT}`))
139+
}
103140
}

0 commit comments

Comments
 (0)