mirror of
https://github.com/jetkvm/cloud-api.git
synced 2026-05-21 05:20:36 +00:00
Upgrade to Express v5, Dockerfile, and Health Checks (#29)
Co-authored-by: Aveline <g@xswan.net> Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com> Co-authored-by: Marc Brooks <IDisposable@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
.github/
|
||||
.idea/
|
||||
node_modules/
|
||||
dist/
|
||||
.DS_Store
|
||||
.env
|
||||
+31
-15
@@ -1,23 +1,39 @@
|
||||
PORT=3000
|
||||
DATABASE_URL="postgresql://jetkvm:jetkvm@localhost:5432/jetkvm?schema=public"
|
||||
|
||||
GOOGLE_CLIENT_ID=XXX # Google OIDC Client ID
|
||||
GOOGLE_CLIENT_SECRET=XXX # Google OIDC Client Secret
|
||||
##
|
||||
## Google Auth with OIDC Configuration
|
||||
##
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
API_HOSTNAME=
|
||||
APP_HOSTNAME=
|
||||
|
||||
API_HOSTNAME=XXX # Is needed for the OIDC Callback
|
||||
APP_HOSTNAME=XXX # Is needed for the OIDC Callback
|
||||
##
|
||||
## Cloudflare TURN Service
|
||||
##
|
||||
CLOUDFLARE_TURN_ID=
|
||||
CLOUDFLARE_TURN_TOKEN=
|
||||
|
||||
CLOUDFLARE_TURN_ID=XXX # Cloudflare TURN ID
|
||||
CLOUDFLARE_TURN_TOKEN=XXX # Cloudflare TURN Token
|
||||
##
|
||||
## Session Cookie Secret
|
||||
##
|
||||
COOKIE_SECRET=
|
||||
|
||||
COOKIE_SECRET=XXX # Session Cookie Secret
|
||||
###
|
||||
### S3 Compatible Storage
|
||||
###
|
||||
R2_ENDPOINT=
|
||||
R2_ACCESS_KEY_ID=
|
||||
R2_SECRET_ACCESS_KEY=
|
||||
R2_BUCKET=
|
||||
R2_CDN_URL=
|
||||
|
||||
R2_ENDPOINT=XXX # Any S3 compatible endpoint
|
||||
R2_ACCESS_KEY_ID=XXX # Any S3 compatible access key
|
||||
R2_SECRET_ACCESS_KEY=XXX # Any S3 compatible secret access key
|
||||
R2_BUCKET=XXX # Any S3 compatible bucket
|
||||
R2_CDN_URL=XXX # Any S3 compatible CDN URL
|
||||
# Allowed CORS Origins, split by comma
|
||||
CORS_ORIGINS=https://app.jetkvm.com,http://localhost:5173
|
||||
|
||||
CORS_ORIGINS=https://app.jetkvm.com,http://localhost:5173 # Allowed CORS Origins, split by comma
|
||||
# Real IP Header for the reverse proxy (e.g. X-Real-IP), leave empty if not needed
|
||||
REAL_IP_HEADER=
|
||||
|
||||
REAL_IP_HEADER=XXX # Real IP Header for the reverse proxy (e.g. X-Real-IP), leave empty if not needed
|
||||
ICE_SERVERS=XXX # ICE Servers for WebRTC, split by comma (e.g. stun:stun.l.google.com:19302,stun:stun1.l.google.com:19302)
|
||||
# ICE Servers for WebRTC, split by comma (e.g. stun:stun.l.google.com:19302,stun:stun1.l.google.com:19302)
|
||||
ICE_SERVERS=
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
.idea
|
||||
dist/
|
||||
.env
|
||||
.env.development
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
FROM node:21.1.0-alpine AS packages
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY LICENSE /usr/src/app/
|
||||
COPY package.json /usr/src/app/
|
||||
COPY package-lock.json /usr/src/app/
|
||||
|
||||
RUN npm install
|
||||
|
||||
FROM packages AS builder
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY . /usr/src/app/
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
FROM packages AS app
|
||||
LABEL org.opencontainers.image.source="https://github.com/jetkvm/cloud-api"
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY --from=builder /usr/src/app/prisma /usr/src/app/prisma
|
||||
COPY --from=builder /usr/src/app/node_modules/.prisma /usr/src/app/node_modules/.prisma
|
||||
COPY --from=builder /usr/src/app/dist /usr/src/app/dist
|
||||
COPY .env.example ./.env
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
EXPOSE 3000
|
||||
CMD ["node", "./dist/index.js"]
|
||||
@@ -26,7 +26,7 @@ If you've found an issue and want to report it, please check our [Issues](https:
|
||||
|
||||
## Development
|
||||
|
||||
This project is built with Node.JS, Prisma and Express.
|
||||
This project is built on Node.JS using Prisma and Express.
|
||||
|
||||
To start the development server, run:
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
name: jetkvm-cloud-api
|
||||
|
||||
networks:
|
||||
jetkvm:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: jetkvm
|
||||
POSTGRES_USER: jetkvm
|
||||
POSTGRES_DB: jetkvm
|
||||
#ports:
|
||||
# - "5432:5432"
|
||||
networks:
|
||||
- jetkvm
|
||||
volumes:
|
||||
- postgresql:/var/lib/postgresql/data
|
||||
|
||||
app: &app
|
||||
build: .
|
||||
environment:
|
||||
PORT: 5172
|
||||
DATABASE_URL: postgres://jetkvm:jetkvm@db:5432/jetkvm
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- "5172:5172"
|
||||
networks:
|
||||
- jetkvm
|
||||
|
||||
# Trigger prisma migration
|
||||
# This can be done in the app container as well, but is generally discouraged.
|
||||
app-migrate:
|
||||
<<: *app
|
||||
command: npm run prisma-migrate
|
||||
ports: []
|
||||
restart: no
|
||||
|
||||
volumes:
|
||||
postgresql:
|
||||
driver: local
|
||||
Generated
+465
-274
File diff suppressed because it is too large
Load Diff
+14
-9
@@ -1,19 +1,23 @@
|
||||
{
|
||||
"name": "jetkvm-cloud-api",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"description": "JetKVM Cloud API and Websocket Server",
|
||||
"main": "dist/src/index.js",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production node -r ts-node/register ./src/index.ts",
|
||||
"dev": "NODE_ENV=development node --watch --watch-path=./src --env-file=.env.development -r ts-node/register ./src/index.ts",
|
||||
"dev:debug": "NODE_ENV=development node --watch --watch-path=./src --env-file=.env.development -r ts-node/register ./src/index.ts --inspect-brk"
|
||||
"dev:debug": "NODE_ENV=development node --watch --watch-path=./src --env-file=.env.development -r ts-node/register ./src/index.ts --inspect-brk",
|
||||
"prisma-dev": "prisma generate --watch",
|
||||
"prisma-dev-migrate": "prisma migrate dev",
|
||||
"prisma-migrate": "prisma migrate deploy",
|
||||
"build": "tsc"
|
||||
},
|
||||
"engines": {
|
||||
"node": "21.1.0"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"author": "JetKVM",
|
||||
"license": "GPL-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.654.0",
|
||||
"@prisma/client": "^5.13.0",
|
||||
@@ -24,22 +28,23 @@
|
||||
"@types/ws": "^8.5.10",
|
||||
"cookie-session": "^2.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^5",
|
||||
"helmet": "^7.1.0",
|
||||
"http-proxy-middleware": "^3.0.0",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
"jose": "^5.2.4",
|
||||
"openid-client": "^5.6.5",
|
||||
"prettier": "3.2.5",
|
||||
"prisma": "^5.13.0",
|
||||
"semver": "^7.6.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5",
|
||||
"ws": "^8.17.0"
|
||||
"ws": "^8.17.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.2.5",
|
||||
"@types/semver": "^7.5.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ declare global {
|
||||
|
||||
// This is needed because in development we don't want to restart
|
||||
// the server with every change, but we want to make sure we don't
|
||||
// create a new connectison to the DB with every change either.
|
||||
// create a new connection to the DB with every change either.
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
prismaClient = new PrismaClient();
|
||||
prismaClient.$connect();
|
||||
|
||||
+33
-29
@@ -3,6 +3,7 @@ import cors from "cors";
|
||||
import cookieSession from "cookie-session";
|
||||
import * as jose from "jose";
|
||||
import helmet from "helmet";
|
||||
import 'dotenv/config';
|
||||
|
||||
import * as Devices from "./devices";
|
||||
import * as OIDC from "./oidc";
|
||||
@@ -18,6 +19,7 @@ declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: "development" | "production";
|
||||
PORT: string;
|
||||
|
||||
API_HOSTNAME: string;
|
||||
APP_HOSTNAME: string;
|
||||
@@ -47,6 +49,8 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
const app = express();
|
||||
app.use(helmet());
|
||||
app.disable("x-powered-by");
|
||||
@@ -74,25 +78,25 @@ export const cookieSessionMiddleware = cookieSession({
|
||||
|
||||
app.use(cookieSessionMiddleware);
|
||||
|
||||
function asyncHandler(fn: any) {
|
||||
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
return Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
}
|
||||
|
||||
// express-session won't sent the cookie, as it's `secure` and `secureProxy` is set to true
|
||||
// DO Apps doesn't send a X-Forwarded-Proto header, so we simply need to make a blanket trust
|
||||
app.set("trust proxy", true);
|
||||
|
||||
const asyncAuthGuard = asyncHandler(authenticated);
|
||||
app.get("/", (req, res) => {
|
||||
return res.status(200).send("OK");
|
||||
});
|
||||
|
||||
app.get("/healthz", (req, res) => {
|
||||
return res.status(200).send({
|
||||
ready: true,
|
||||
time: new Date()
|
||||
})
|
||||
});
|
||||
|
||||
app.get(
|
||||
"/me",
|
||||
asyncAuthGuard,
|
||||
asyncHandler(async (req: express.Request, res: express.Response) => {
|
||||
authenticated,
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const idToken = req.session?.id_token;
|
||||
const { sub, iss, exp, aud, iat, jti, nbf } = jose.decodeJwt(idToken);
|
||||
|
||||
@@ -105,32 +109,32 @@ app.get(
|
||||
}
|
||||
|
||||
return res.json({ ...user, sub });
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
app.get("/releases", asyncHandler(Releases.Retrieve));
|
||||
app.get("/releases", Releases.Retrieve);
|
||||
app.get(
|
||||
"/releases/system_recovery/latest",
|
||||
asyncHandler(Releases.RetrieveLatestSystemRecovery),
|
||||
Releases.RetrieveLatestSystemRecovery,
|
||||
);
|
||||
app.get("/releases/app/latest", asyncHandler(Releases.RetrieveLatestApp));
|
||||
app.get("/releases/app/latest", Releases.RetrieveLatestApp);
|
||||
|
||||
app.get("/devices", asyncAuthGuard, asyncHandler(Devices.List));
|
||||
app.get("/devices/:id", asyncAuthGuard, asyncHandler(Devices.Retrieve));
|
||||
app.post("/devices/token", asyncHandler(Devices.Token));
|
||||
app.put("/devices/:id", asyncAuthGuard, asyncHandler(Devices.Update));
|
||||
app.delete("/devices/:id", asyncHandler(Devices.Delete));
|
||||
app.get("/devices", authenticated, Devices.List);
|
||||
app.get("/devices/:id", authenticated, Devices.Retrieve);
|
||||
app.post("/devices/token", Devices.Token);
|
||||
app.put("/devices/:id", authenticated, Devices.Update);
|
||||
app.delete("/devices/:id", Devices.Delete);
|
||||
|
||||
app.post("/webrtc/session", asyncAuthGuard, asyncHandler(Webrtc.CreateSession));
|
||||
app.post("/webrtc/ice_config", asyncAuthGuard, asyncHandler(Webrtc.CreateIceCredentials));
|
||||
app.post("/webrtc/session", authenticated, Webrtc.CreateSession);
|
||||
app.post("/webrtc/ice_config", authenticated, Webrtc.CreateIceCredentials);
|
||||
app.post(
|
||||
"/webrtc/turn_activity",
|
||||
asyncAuthGuard,
|
||||
asyncHandler(Webrtc.CreateTurnActivity),
|
||||
authenticated,
|
||||
Webrtc.CreateTurnActivity,
|
||||
);
|
||||
|
||||
app.post("/oidc/google", asyncHandler(OIDC.Google));
|
||||
app.get("/oidc/callback_o", asyncHandler(OIDC.Callback));
|
||||
app.post("/oidc/google", OIDC.Google);
|
||||
app.get("/oidc/callback_o", OIDC.Callback);
|
||||
app.get("/oidc/callback", (req, res) => {
|
||||
/*
|
||||
* We set the session cookie in the /oidc/google route as a part of 302 redirect to the OIDC login page
|
||||
@@ -177,10 +181,10 @@ app.get("/oidc/callback", (req, res) => {
|
||||
|
||||
app.post(
|
||||
"/logout",
|
||||
asyncHandler((req: express.Request, res: express.Response) => {
|
||||
(req: express.Request, res: express.Response) => {
|
||||
req.session = null;
|
||||
return res.json({ message: "Logged out" });
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
// Error-handling middleware
|
||||
@@ -190,7 +194,7 @@ app.use(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
) => {
|
||||
): void => {
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const statusCode = err instanceof HttpError ? err.status : 500;
|
||||
|
||||
@@ -207,8 +211,8 @@ app.use(
|
||||
},
|
||||
);
|
||||
|
||||
const server = app.listen(3000, () => {
|
||||
console.log("Server started on port 3000");
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log("Server started on port " + PORT);
|
||||
});
|
||||
|
||||
initializeWebRTCSignaling(server);
|
||||
|
||||
+5
-1
@@ -1,8 +1,12 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "es5",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext"
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"sourceMap": false,
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
||||
Reference in New Issue
Block a user