docker & prod building 🎉
This commit is contained in:
parent
613b75edb6
commit
004e4926c4
|
@ -0,0 +1,10 @@
|
||||||
|
# Canvas
|
||||||
|
|
||||||
|
## Running via Docker Compose
|
||||||
|
|
||||||
|
1. Run `npm run build:all`
|
||||||
|
2. Run `npm run build:docker`
|
||||||
|
3. Run `docker compose run --rm canvas npx prisma migrate deploy`
|
||||||
|
4. (optional) Load default palette colors
|
||||||
|
Run `docker compose run --rm canvas npm run -w packages/server prisma:seed:palette`
|
||||||
|
5. Run `docker compose up -d`
|
|
@ -0,0 +1,34 @@
|
||||||
|
# this docker-compose does not include a build for the Canvas image
|
||||||
|
# generate the image via a build script
|
||||||
|
|
||||||
|
name: canvas
|
||||||
|
|
||||||
|
services:
|
||||||
|
canvas:
|
||||||
|
image: sc07/canvas
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- SESSION_SECRET=CHANGE ME TO RANDOM VALUE
|
||||||
|
- REDIS_HOST=redis://redis
|
||||||
|
- DATABASE_URL=postgres://postgres@postgres/canvas
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- postgres
|
||||||
|
redis:
|
||||||
|
restart: always
|
||||||
|
image: redis:7-alpine
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'redis-cli', 'ping']
|
||||||
|
volumes:
|
||||||
|
- ./data/redis:/data
|
||||||
|
postgres:
|
||||||
|
restart: always
|
||||||
|
image: postgres:14-alpine
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'pg_isready', '-U', 'postgres']
|
||||||
|
volumes:
|
||||||
|
- ./data/postgres:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
- 'POSTGRES_HOST_AUTH_METHOD=trust'
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,13 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:client": "npm run dev -w packages/client",
|
"dev:client": "npm run dev -w packages/client",
|
||||||
"dev:server": "npm run dev -w packages/server",
|
"dev:server": "npm run dev -w packages/server",
|
||||||
"prisma:studio": "npm run prisma:studio -w packages/server"
|
"prisma:studio": "npm run prisma:studio -w packages/server",
|
||||||
|
"build:all": "./packages/build/build-all.sh",
|
||||||
|
"build:docker": "./packages/build/docker-build.sh",
|
||||||
|
"build:lib": "npm run build -w packages/lib",
|
||||||
|
"build:client": "npm run build -w packages/client",
|
||||||
|
"build:admin": "npm run build -w packages/admin",
|
||||||
|
"build:server": "npm run build -w packages/server"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# expose APP_ROOT to client, for routing
|
||||||
|
VITE_APP_ROOT=$APP_ROOT
|
|
@ -8,22 +8,27 @@ import { Root } from "./Root.tsx";
|
||||||
import { HomePage } from "./pages/Home/page.tsx";
|
import { HomePage } from "./pages/Home/page.tsx";
|
||||||
import { AccountsPage } from "./pages/Accounts/Accounts/page.tsx";
|
import { AccountsPage } from "./pages/Accounts/Accounts/page.tsx";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <Root />,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <HomePage />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/accounts",
|
||||||
|
element: <AccountsPage />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
{
|
{
|
||||||
path: "/",
|
basename: import.meta.env.VITE_APP_ROOT,
|
||||||
element: <Root />,
|
}
|
||||||
children: [
|
);
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
element: <HomePage />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/accounts",
|
|
||||||
element: <AccountsPage />,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
|
@ -5,8 +5,10 @@ import react from "@vitejs/plugin-react";
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
root: "src",
|
root: "src",
|
||||||
envDir: "..",
|
envDir: "..",
|
||||||
|
base: process.env.APP_ROOT,
|
||||||
build: {
|
build: {
|
||||||
outDir: "../dist",
|
outDir: "../dist",
|
||||||
|
emptyOutDir: true,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
react({
|
react({
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# this file needs to be copied to /build
|
||||||
|
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
COPY --chown=node:node . /home/node/app/
|
||||||
|
USER node
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
RUN npx prisma generate
|
||||||
|
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENV SERVE_CLIENT /home/node/app/packages/client
|
||||||
|
ENV SERVE_ADMIN /home/node/app/packages/admin
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD [ "npm", "-w", "packages/server", "start" ]
|
|
@ -0,0 +1,68 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# builds client, admin & server into one folder
|
||||||
|
# - client is mounted at /
|
||||||
|
# - admin is mounted at /admin
|
||||||
|
|
||||||
|
# ensure we are in packages/build
|
||||||
|
MY_DIR="$(cd "$(dirname "$0")"; pwd)"
|
||||||
|
OUT_DIR="$(cd "$MY_DIR/../../build"; pwd)"
|
||||||
|
cd $MY_DIR
|
||||||
|
|
||||||
|
# empty out directory
|
||||||
|
rm -rf $OUT_DIR/*
|
||||||
|
mkdir -p $OUT_DIR/packages/lib
|
||||||
|
mkdir -p $OUT_DIR/packages/client
|
||||||
|
mkdir -p $OUT_DIR/packages/admin
|
||||||
|
mkdir -p $OUT_DIR/packages/server
|
||||||
|
mkdir -p $OUT_DIR/prisma
|
||||||
|
|
||||||
|
cp $MY_DIR/../../package.json $MY_DIR/../../package-lock.json $OUT_DIR/
|
||||||
|
|
||||||
|
LIB_DIR="$MY_DIR/../../packages/lib"
|
||||||
|
CLIENT_DIR="$MY_DIR/../../packages/client"
|
||||||
|
ADMIN_DIR="$MY_DIR/../../packages/admin"
|
||||||
|
SERVER_DIR="$MY_DIR/../../packages/server"
|
||||||
|
PRISMA_DIR="$SERVER_DIR/prisma"
|
||||||
|
|
||||||
|
cp -r $PRISMA_DIR/schema.prisma $PRISMA_DIR/migrations $OUT_DIR/prisma/
|
||||||
|
|
||||||
|
# --- Shared Library ---
|
||||||
|
|
||||||
|
echo "Building lib..."
|
||||||
|
|
||||||
|
cd "$MY_DIR/../.." && npm run-script build:lib
|
||||||
|
cd $LIB_DIR
|
||||||
|
mv dist $OUT_DIR/packages/lib
|
||||||
|
cp package.json $OUT_DIR/packages/lib/
|
||||||
|
|
||||||
|
# janky? fix to keep imports in dev
|
||||||
|
sed -i -e 's/"main": ".*"/"main": ".\/dist\/index.js"/' $OUT_DIR/packages/lib/package.json
|
||||||
|
|
||||||
|
# --- Main Client ---
|
||||||
|
|
||||||
|
echo "Building client..."
|
||||||
|
|
||||||
|
cd "$MY_DIR/../.." && npm run-script build:client
|
||||||
|
cd $CLIENT_DIR
|
||||||
|
mv dist/* $OUT_DIR/packages/client
|
||||||
|
rm -r dist # this dir is empty, delete it to prevent confusion
|
||||||
|
|
||||||
|
# --- Admin Client ---
|
||||||
|
|
||||||
|
echo "Building admin..."
|
||||||
|
|
||||||
|
cd "$MY_DIR/../../" && APP_ROOT=/admin npm run-script build:admin
|
||||||
|
cd $ADMIN_DIR
|
||||||
|
mv dist/* $OUT_DIR/packages/admin
|
||||||
|
rm -r dist # this dir is empty, delete it to prevent confusion
|
||||||
|
|
||||||
|
# --- Server ---
|
||||||
|
|
||||||
|
echo "Building server..."
|
||||||
|
|
||||||
|
cd "$MY_DIR/../../" && npm run-script build:server
|
||||||
|
cd $SERVER_DIR
|
||||||
|
mv dist $OUT_DIR/packages/server
|
||||||
|
cp package.json tool.sh $OUT_DIR/packages/server
|
||||||
|
# rm -r dist # this dir is empty, delete it to prevent confusion
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
MY_DIR="$(cd "$(dirname "$0")"; pwd)"
|
||||||
|
OUT_DIR="$(cd "$MY_DIR/../../build"; pwd)"
|
||||||
|
cd $MY_DIR
|
||||||
|
|
||||||
|
cp Dockerfile $OUT_DIR/
|
||||||
|
cd $OUT_DIR
|
||||||
|
|
||||||
|
docker build . -t sc07/canvas
|
|
@ -6,6 +6,7 @@ export default defineConfig({
|
||||||
envDir: "..",
|
envDir: "..",
|
||||||
build: {
|
build: {
|
||||||
outDir: "../dist",
|
outDir: "../dist",
|
||||||
|
emptyOutDir: true,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
react({
|
react({
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
"name": "@sc07-canvas/lib",
|
"name": "@sc07-canvas/lib",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eventemitter3": "^5.0.1"
|
"eventemitter3": "^5.0.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
import * as net from "./net";
|
import * as net from "./net";
|
||||||
|
import { CanvasLib } from "./canvas";
|
||||||
|
|
||||||
export { net };
|
export { net, CanvasLib };
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/recommended/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"inlineSourceMap": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,11 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "@sc07-canvas/server",
|
"name": "@sc07-canvas/server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "./build/index.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "DOTENV_CONFIG_PATH=.env.local nodemon -r dotenv/config src/index.ts",
|
"dev": "DOTENV_CONFIG_PATH=.env.local nodemon -r dotenv/config src/index.ts",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"build": "tsc",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"prisma:studio": "prisma studio"
|
"prisma:studio": "prisma studio",
|
||||||
|
"prisma:migrate": "prisma migrate deploy",
|
||||||
|
"prisma:seed:palette": "./tool.sh seed_palette"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
@ -21,7 +24,6 @@
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
"prettier": "^3.0.1",
|
"prettier": "^3.0.1",
|
||||||
"prisma": "^5.3.1",
|
"prisma": "^5.3.1",
|
||||||
"prisma-dbml-generator": "^0.12.0",
|
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
|
@ -31,6 +33,7 @@
|
||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
|
"prisma-dbml-generator": "^0.12.0",
|
||||||
"redis": "^4.6.12",
|
"redis": "^4.6.12",
|
||||||
"socket.io": "^4.7.2",
|
"socket.io": "^4.7.2",
|
||||||
"winston": "^3.11.0"
|
"winston": "^3.11.0"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import http from "node:http";
|
import http from "node:http";
|
||||||
|
import path from "node:path";
|
||||||
import express, { type Express } from "express";
|
import express, { type Express } from "express";
|
||||||
import expressSession from "express-session";
|
import expressSession from "express-session";
|
||||||
import RedisStore from "connect-redis";
|
import RedisStore from "connect-redis";
|
||||||
|
@ -28,6 +29,49 @@ export class ExpressServer {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
this.httpServer = http.createServer(this.app);
|
this.httpServer = http.createServer(this.app);
|
||||||
|
|
||||||
|
if (process.env.SERVE_CLIENT) {
|
||||||
|
// client is needing to serve
|
||||||
|
Logger.info(
|
||||||
|
"Serving client UI at / using root " +
|
||||||
|
path.join(__dirname, process.env.SERVE_CLIENT)
|
||||||
|
);
|
||||||
|
this.app.use(express.static(process.env.SERVE_CLIENT));
|
||||||
|
} else {
|
||||||
|
this.app.get("/", (req, res) => {
|
||||||
|
res.status(404).contentType("html").send(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Canvas Server</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Canvas Server</h1>
|
||||||
|
<p>This instance is not serving the client</p>
|
||||||
|
<i>This instance might not be configured correctly</i>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.SERVE_ADMIN) {
|
||||||
|
// client is needing to serve
|
||||||
|
Logger.info(
|
||||||
|
"Serving admin UI at /admin using root " +
|
||||||
|
path.join(__dirname, process.env.SERVE_ADMIN)
|
||||||
|
);
|
||||||
|
const assetsDir = path.join(__dirname, process.env.SERVE_ADMIN, "assets");
|
||||||
|
const indexFile = path.join(
|
||||||
|
__dirname,
|
||||||
|
process.env.SERVE_ADMIN,
|
||||||
|
"index.html"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.app.use("/admin/assets", express.static(assetsDir));
|
||||||
|
this.app.use("/admin/*", (req, res) => {
|
||||||
|
res.sendFile(indexFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.app.use(session);
|
this.app.use(session);
|
||||||
this.app.use("/api", APIRoutes);
|
this.app.use("/api", APIRoutes);
|
||||||
|
|
||||||
|
|
|
@ -71,12 +71,20 @@ export class User {
|
||||||
return Date.now() - this._updatedAt >= 1000 * 60;
|
return Date.now() - this._updatedAt >= 1000 * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromAuthSession(auth: AuthSession): Promise<User> {
|
static async fromAuthSession(auth: AuthSession): Promise<User | undefined> {
|
||||||
const user = await this.fromSub(
|
try {
|
||||||
auth.user.username + "@" + auth.service.instance.hostname
|
const user = await this.fromSub(
|
||||||
);
|
auth.user.username + "@" + auth.service.instance.hostname
|
||||||
user.authSession = auth;
|
);
|
||||||
return user;
|
user.authSession = auth;
|
||||||
|
return user;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof UserNotFound) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromSub(sub: string): Promise<User> {
|
static async fromSub(sub: string): Promise<User> {
|
||||||
|
|
|
@ -31,6 +31,15 @@ declare global {
|
||||||
* Specifically setting CORS origin is required because of use of credentials (cookies)
|
* Specifically setting CORS origin is required because of use of credentials (cookies)
|
||||||
*/
|
*/
|
||||||
CLIENT_ORIGIN?: string;
|
CLIENT_ORIGIN?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, use this relative path to serve the client at the root
|
||||||
|
*/
|
||||||
|
SERVE_CLIENT?: string;
|
||||||
|
/**
|
||||||
|
* If set, use this relative path to serve the admin UI at /admin
|
||||||
|
*/
|
||||||
|
SERVE_ADMIN?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"extends": "@tsconfig/recommended/tsconfig.json",
|
"extends": "@tsconfig/recommended/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"outDir": "build"
|
"outDir": "dist",
|
||||||
}
|
"inlineSourceMap": true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue