diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7fd160a..ff8de0a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,11 +1,20 @@ -name: Build & Deploy to Scaleway +name: Build for production on: workflow_dispatch: + envType: + description: "Environment to deploy to" + required: true + default: "prod" + type: choice + options: + - prod + - staging jobs: build: runs-on: ubuntu-latest + environment: ${{ github.event.inputs.envType }} steps: - uses: actions/checkout@v4 - name: Login to Scaleway Container Registry @@ -15,6 +24,6 @@ jobs: password: ${{ secrets.SCALEWAY_API_KEY }} registry: ${{ secrets.CONTAINER_REGISTRY_ENDPOINT }} - name: Build the Docker image - run: docker build . -t ${{ secrets.CONTAINER_REGISTRY_ENDPOINT }}/fo-prod + run: docker build . -t ${{ secrets.CONTAINER_REGISTRY_ENDPOINT }}/fo-${{ github.event.inputs.envType }} - name: Push the Docker Image - run: docker push ${{ secrets.CONTAINER_REGISTRY_ENDPOINT }}/fo-prod + run: docker push ${{ secrets.CONTAINER_REGISTRY_ENDPOINT }}/fo-${{ github.event.inputs.envType }} diff --git a/.gitignore b/.gitignore index a29f5f4..42fcaec 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ public/* # Access access/* +!access/.gitkeep # Misc .DS_Store diff --git a/Dockerfile b/Dockerfile index d476271..5c32b03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,33 @@ # App build FROM node:20-alpine as build-stage -ARG user=node -ARG project_dir=/opt/node_app ARG PORT=3000 +ARG API_URL="http://localhost:3000" -ENV PORT $PORT ENV NODE_OPTIONS="--max_old_space_size=4096" - -EXPOSE $PORT +ENV PORT=$PORT +ENV API_URL=$API_URL ## Enable corepack for proper version of YARN RUN corepack enable -RUN mkdir $project_dir && chown $user:$user $project_dir -USER $user -WORKDIR $project_dir +WORKDIR /app ## Copy file for YARN then install all deps -COPY --chown=$user .yarnrc.yml yarn.lock package.json ./ +COPY .yarnrc.yml yarn.lock package.json ./ RUN yarn install --frozen-lockfile -COPY --chown=$user . ./ - ## Build the app +COPY . ./ RUN yarn run build -CMD ["yarn", "start"] \ No newline at end of file +## Run stage +FROM node:20-alpine + +COPY --from=build-stage /app/public ./public +COPY --from=build-stage /app/node_modules ./node_modules +COPY --from=build-stage /app/access ./access + +EXPOSE $PORT + +CMD ["node", "./public/server"] diff --git a/access/.gitkeep b/access/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/jest/config.js b/jest/config.js index 5d25004..c35b3a7 100644 --- a/jest/config.js +++ b/jest/config.js @@ -24,7 +24,7 @@ module.exports = { __LOCAL__: false, __REGISTER_DISCORD_COMMANDS__: false, __TEST__: true, - API_URL: "http://localhost:3000", + API_URL: "http://localhost:3333", }, maxConcurrency: 50, maxWorkers: 1, diff --git a/src/routes/certbot.ts b/src/routes/certbot.ts deleted file mode 100644 index 14cc350..0000000 --- a/src/routes/certbot.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* Copyright Coplay. All Rights Reserved. Use of this source code is governed by an MIT-style license that can be found in the LICENSE file at https://coplay.org/colicense */ -import { NextFunction, Request, Response, Router } from "express" -import * as path from "path" - -const certbotRouter: Router = Router() - -certbotRouter.use((request: Request, response: Response, _next: NextFunction) => { - const filename = request.originalUrl.replace(/.*\//, "") - const resolvedPath: string = path.resolve(`../certbot/.well-known/acme-challenge/${filename}`) - response.setHeader("Content-Type", "text/html") - return response.sendFile(resolvedPath) -}) - -export default certbotRouter diff --git a/src/server/index.ts b/src/server/index.ts index 22d32c2..a3d21ad 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -8,14 +8,10 @@ import hpp from "hpp" import favicon from "serve-favicon" import chalk from "chalk" import * as http from "http" -import * as https from "https" -import * as fs from "fs" -import _ from "lodash" import devServer from "./devServer" import ssr from "./ssr" -import certbotRouter from "../routes/certbot" import { hasSecret, secure } from "./secure" import { announcementListGet } from "./gsheets/announcements" import { detailedBoxListGet } from "./gsheets/boxes" @@ -54,7 +50,7 @@ import { notificationsSubscribe, notificationMain } from "./notifications" import { /* discordRegisterCommands, */ discordBot, hasDiscordAccess } from "./discordBot" import checkAccess from "./checkAccess" import { hasGSheetsAccess } from "./gsheets/accessors" -import { addStatus, showStatusAt } from "./status" +import { addStatus } from "./status" import { miscDiscordInvitation, miscFestivalDateListGet, @@ -81,10 +77,6 @@ app.use(helmet({ contentSecurityPolicy: false })) app.use(hpp()) // Compress all requests app.use(compression()) -// Https with certbot and Let's Encrypt -if (!__DEV__) { - app.use("/.well-known/acme-challenge", certbotRouter) -} // Use for http request debug (show errors only) app.use(logger("dev", { skip: (_req, res) => res.statusCode < 400 })) @@ -164,70 +156,21 @@ app.post("/notifications/subscribe", notificationsSubscribe) app.get("*", ssr) /** - * Create HTTP and HTTPS server. + * Create server. */ -const servers = [{ protocol: "http", server: http.createServer(app) }] - -interface Cert { - key: string - cert: string -} -const certPaths: Cert[] = [ - { - // Prod - key: "/root/certbot/config/live/fo.parisestludique.fr/privkey.pem", - cert: "/root/certbot/config/live/fo.parisestludique.fr/fullchain.pem", - }, - { - // Local - key: "../certbot/key.pem", - cert: "../certbot/cert.pem", - }, -] -const validCertPath: Cert | undefined = certPaths.find((certPath: Cert) => - _.every(certPath, (pemPath: string) => fs.existsSync(pemPath)) -) -if (validCertPath) { - const httpsOptions = _.mapValues(validCertPath, (pemPath: string) => fs.readFileSync(pemPath)) - - servers.push({ protocol: "https", server: https.createServer(httpsOptions, app) }) - - showStatusAt(6) -} else { - showStatusAt(5) -} - -/** - * Listen on provided port, on all network interfaces. - */ -servers.forEach(({ protocol, server }) => { - const port = Number(process.env.PORT) || 3000 - - server.listen(protocol === "http" ? port : port + 2) - server.on("error", onError) - server.on("listening", () => onListening(server)) -}) - -/** - * Event listener for HTTP server 'error' event. - */ - -function onError(error: any) { +const server = http.createServer(app) +server.listen(process.env.PORT || 3000) +server.on("error", (error: any) => { if (error) { addStatus("Server listening:", chalk.red(`==> 😭 OMG!!! ${error}`)) } -} - -/** - * Event listener for HTTP server 'listening' event. - */ - -function onListening(server: any) { +}) +server.on("listening", () => { const addr = server.address() - const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr.port}` + const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr?.port}` addStatus("Server listening:", chalk.green(`✅ ${bind}`)) -} +}) if (hasGSheetsAccess()) { addStatus("Database:", chalk.green(`✅ online from Google Sheet`)) diff --git a/webpack/base.config.ts b/webpack/base.config.ts index 4057a42..761eedc 100644 --- a/webpack/base.config.ts +++ b/webpack/base.config.ts @@ -52,7 +52,7 @@ const getPlugins = (isWeb: boolean) => { __DEV__: isDev, __LOCAL__: isLocal, __REGISTER_DISCORD_COMMANDS__: isRegisterDiscordCommands, - API_URL: process.env.API_URL || "'http://localhost:3000'", + API_URL: `"${process.env.API_URL}"` || "'http://localhost:3000'", }), ]