Feat: Dockerizing using nginx reverse proxy (#280)

* minor docker fixes

* eslint config changes

* dockerfile changes to backend and frontend

* oauth enabled env flag

* sentry enabled env flag

* build: get alternatives for environment variables and static file storage

* build: automatically generate random secret key if not provided

* build: update docker compose for next url env add channels to requirements for asgi server and save files in local machine for docker environment

* build: update nginx conf for backend base url update backend dockerfile to make way for static file uploads

* feat: create a default user with given values else default values

* chore: update docker python version and other dependency version in docker

* build: update local settings file to run it in docker

* fix: update script to run in default production setting

* fix: env variable changes and env setup shell script added

* Added Single Dockerfile to run the Entire plane application

* docs build fixes

---------

Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
This commit is contained in:
sriram veeraghanta 2023-02-21 11:31:43 +05:30 committed by GitHub
parent 33e2986062
commit bdca84bd09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 9613 additions and 11018 deletions

View File

@ -1,10 +1,10 @@
module.exports = { module.exports = {
root: true, root: true,
// This tells ESLint to load the config from the package `config` // This tells ESLint to load the config from the package `eslint-config-custom`
// extends: ["custom"], extends: ["custom"],
settings: { settings: {
next: { next: {
rootDir: ["apps/*/"], rootDir: ["apps/*"],
}, },
}, },
}; };

5
.gitignore vendored
View File

@ -65,3 +65,8 @@ package-lock.json
# Sentry # Sentry
.sentryclirc .sentryclirc
# lock files
package-lock.json
pnpm-lock.yaml
pnpm-workspace.yaml

116
Dockerfile Normal file
View File

@ -0,0 +1,116 @@
FROM node:18-alpine AS builder
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
WORKDIR /app
RUN yarn global add turbo
COPY . .
RUN turbo prune --scope=app --docker
# Add lockfile and package.json's of isolated subworkspace
FROM node:18-alpine AS installer
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install
# Build the project
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
RUN yarn turbo run build --filter=app
FROM python:3.11.1-alpine3.17 AS backend
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /code
RUN apk --update --no-cache add \
"libpq~=15" \
"libxslt~=1.1" \
"nodejs-current~=19" \
"xmlsec~=1.2" \
"nginx" \
"nodejs" \
"npm" \
"supervisor"
COPY apiserver/requirements.txt ./
COPY apiserver/requirements ./requirements
RUN apk add libffi-dev
RUN apk --update --no-cache --virtual .build-deps add \
"bash~=5.2" \
"g++~=12.2" \
"gcc~=12.2" \
"cargo~=1.64" \
"git~=2" \
"make~=4.3" \
"postgresql13-dev~=13" \
"libc-dev" \
"linux-headers" \
&& \
pip install -r requirements.txt --compile --no-cache-dir \
&& \
apk del .build-deps
# Add in Django deps and generate Django's static files
COPY apiserver/manage.py manage.py
COPY apiserver/plane plane/
COPY apiserver/templates templates/
COPY apiserver/gunicorn.config.py ./
RUN apk --update --no-cache add "bash~=5.2"
COPY apiserver/bin ./bin/
RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod -R 777 /code
# Expose container port and run entry point script
EXPOSE 8000
EXPOSE 3000
EXPOSE 80
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 plane
RUN adduser --system --uid 1001 captain
COPY --from=installer /app/apps/app/next.config.js .
COPY --from=installer /app/apps/app/package.json .
COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone ./
COPY --from=installer --chown=captain:plane /app/apps/app/.next/static ./apps/app/.next/static
ENV NEXT_TELEMETRY_DISABLED 1
# RUN rm /etc/nginx/conf.d/default.conf
#######################################################################
COPY nginx/nginx-single-docker-image.conf /etc/nginx/http.d/default.conf
#######################################################################
COPY nginx/supervisor.conf /code/supervisor.conf
CMD ["supervisord","-c","/code/supervisor.conf"]

View File

@ -1,18 +1,22 @@
# Backend
SECRET_KEY="<-- django secret -->" SECRET_KEY="<-- django secret -->"
DJANGO_SETTINGS_MODULE="plane.settings.production"
# Database
DATABASE_URL=postgres://plane:plane@plane-db-1:5432/plane
# Cache
REDIS_URL=redis://redis:6379/
# SMPT
EMAIL_HOST="<-- email smtp -->" EMAIL_HOST="<-- email smtp -->"
EMAIL_HOST_USER="<-- email host user -->" EMAIL_HOST_USER="<-- email host user -->"
EMAIL_HOST_PASSWORD="<-- email host password -->" EMAIL_HOST_PASSWORD="<-- email host password -->"
# AWS
AWS_REGION="<-- aws region -->" AWS_REGION="<-- aws region -->"
AWS_ACCESS_KEY_ID="<-- aws access key -->" AWS_ACCESS_KEY_ID="<-- aws access key -->"
AWS_SECRET_ACCESS_KEY="<-- aws secret acess key -->" AWS_SECRET_ACCESS_KEY="<-- aws secret acess key -->"
AWS_S3_BUCKET_NAME="<-- aws s3 bucket name -->" AWS_S3_BUCKET_NAME="<-- aws s3 bucket name -->"
# FE
SENTRY_DSN="<-- sentry dsn -->" WEB_URL="localhost/"
WEB_URL="<-- frontend web url -->" # OAUTH
GITHUB_CLIENT_SECRET="<-- github secret -->" GITHUB_CLIENT_SECRET="<-- github secret -->"
# Flags
DISABLE_COLLECTSTATIC=1 DISABLE_COLLECTSTATIC=1
DOCKERIZED=0 //True if running docker compose else 0 DOCKERIZED=1

View File

@ -1,4 +1,4 @@
FROM python:3.8.14-alpine3.16 AS backend FROM python:3.11.1-alpine3.17 AS backend
# set environment variables # set environment variables
ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONDONTWRITEBYTECODE 1
@ -8,19 +8,19 @@ ENV PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /code WORKDIR /code
RUN apk --update --no-cache add \ RUN apk --update --no-cache add \
"libpq~=14" \ "libpq~=15" \
"libxslt~=1.1" \ "libxslt~=1.1" \
"nodejs-current~=18" \ "nodejs-current~=19" \
"xmlsec~=1.2" "xmlsec~=1.2"
COPY requirements.txt ./ COPY requirements.txt ./
COPY requirements ./requirements COPY requirements ./requirements
RUN apk add libffi-dev RUN apk add libffi-dev
RUN apk --update --no-cache --virtual .build-deps add \ RUN apk --update --no-cache --virtual .build-deps add \
"bash~=5.1" \ "bash~=5.2" \
"g++~=11.2" \ "g++~=12.2" \
"gcc~=11.2" \ "gcc~=12.2" \
"cargo~=1.60" \ "cargo~=1.64" \
"git~=2" \ "git~=2" \
"make~=4.3" \ "make~=4.3" \
"postgresql13-dev~=13" \ "postgresql13-dev~=13" \
@ -46,15 +46,16 @@ COPY templates templates/
COPY gunicorn.config.py ./ COPY gunicorn.config.py ./
USER root USER root
RUN apk --update --no-cache add "bash~=5.1" RUN apk --update --no-cache add "bash~=5.2"
COPY ./bin ./bin/ COPY ./bin ./bin/
RUN chmod +x ./bin/takeoff ./bin/worker RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod -R 777 /code
USER captain USER captain
# Expose container port and run entry point script # Expose container port and run entry point script
EXPOSE 8000 EXPOSE 8000
CMD [ "./bin/takeoff" ] # CMD [ "./bin/takeoff" ]

View File

@ -2,4 +2,8 @@
set -e set -e
python manage.py wait_for_db python manage.py wait_for_db
python manage.py migrate python manage.py migrate
# Create a Default User
python bin/user_script.py
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --config gunicorn.config.py --max-requests 1200 --max-requests-jitter 1000 --access-logfile - exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --config gunicorn.config.py --max-requests 1200 --max-requests-jitter 1000 --access-logfile -

View File

@ -0,0 +1,28 @@
import os, sys
import uuid
sys.path.append("/code")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
import django
django.setup()
from plane.db.models import User
def populate():
default_email = os.environ.get("DEFAULT_EMAIL", "captain@plane.so")
default_password = os.environ.get("DEFAULT_PASSWORD", "password123")
if not User.objects.filter(email=default_email).exists():
user = User.objects.create(email=default_email, username=uuid.uuid4().hex)
user.set_password(default_password)
user.save()
print("User created")
print("Success")
if __name__ == "__main__":
populate()

View File

@ -1,12 +1,13 @@
import os import os
import datetime import datetime
from datetime import timedelta from datetime import timedelta
from django.core.management.utils import get_random_secret_key
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = os.environ.get("SECRET_KEY") SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key())
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True

View File

@ -2,6 +2,7 @@
from __future__ import absolute_import from __future__ import absolute_import
import dj_database_url
import sentry_sdk import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration from sentry_sdk.integrations.redis import RedisIntegration
@ -24,6 +25,10 @@ DATABASES = {
} }
} }
DOCKERIZED = os.environ.get("DOCKERIZED", False)
if DOCKERIZED:
DATABASES["default"] = dj_database_url.config()
CACHES = { CACHES = {
"default": { "default": {
@ -41,6 +46,7 @@ INTERNAL_IPS = ("127.0.0.1",)
CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_ALLOW_ALL = True
if os.environ.get("SENTRY_DSN", False):
sentry_sdk.init( sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN"), dsn=os.environ.get("SENTRY_DSN"),
integrations=[DjangoIntegration(), RedisIntegration()], integrations=[DjangoIntegration(), RedisIntegration()],
@ -64,5 +70,10 @@ RQ_QUEUES = {
}, },
} }
WEB_URL = "http://localhost:3000" MEDIA_URL = "/uploads/"
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads")
if DOCKERIZED:
REDIS_URL = os.environ.get("REDIS_URL")
WEB_URL = os.environ.get("WEB_URL", "localhost:3000")

View File

@ -33,6 +33,10 @@ CORS_ORIGIN_WHITELIST = [
DATABASES["default"] = dj_database_url.config() DATABASES["default"] = dj_database_url.config()
SITE_ID = 1 SITE_ID = 1
DOCKERIZED = os.environ.get(
"DOCKERIZED", False
) # Set the variable true if running in docker-compose environment
# Enable Connection Pooling (if desired) # Enable Connection Pooling (if desired)
# DATABASES['default']['ENGINE'] = 'django_postgrespool' # DATABASES['default']['ENGINE'] = 'django_postgrespool'
@ -48,9 +52,9 @@ CORS_ALLOW_ALL_ORIGINS = True
# Simplified static file serving. # Simplified static file serving.
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
if os.environ.get("SENTRY_DSN", False):
sentry_sdk.init( sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN"), dsn=os.environ.get("SENTRY_DSN", ""),
integrations=[DjangoIntegration(), RedisIntegration()], integrations=[DjangoIntegration(), RedisIntegration()],
# If you wish to associate users to errors (assuming you are using # If you wish to associate users to errors (assuming you are using
# django.contrib.auth) you may enable sending PII data. # django.contrib.auth) you may enable sending PII data.
@ -59,21 +63,26 @@ sentry_sdk.init(
environment="production", environment="production",
) )
if (
os.environ.get("AWS_REGION", False)
and os.environ.get("AWS_ACCESS_KEY_ID", False)
and os.environ.get("AWS_SECRET_ACCESS_KEY", False)
and os.environ.get("AWS_S3_BUCKET_NAME", False)
):
# The AWS region to connect to. # The AWS region to connect to.
AWS_REGION = os.environ.get("AWS_REGION") AWS_REGION = os.environ.get("AWS_REGION", "")
# The AWS access key to use. # The AWS access key to use.
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID") AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "")
# The AWS secret access key to use. # The AWS secret access key to use.
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY") AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
# The optional AWS session token to use. # The optional AWS session token to use.
# AWS_SESSION_TOKEN = "" # AWS_SESSION_TOKEN = ""
# The name of the bucket to store files in. # The name of the bucket to store files in.
AWS_S3_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME") AWS_S3_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "")
# How to construct S3 URLs ("auto", "path", "virtual"). # How to construct S3 URLs ("auto", "path", "virtual").
AWS_S3_ADDRESSING_STYLE = "auto" AWS_S3_ADDRESSING_STYLE = "auto"
@ -142,6 +151,12 @@ AWS_S3_FILE_OVERWRITE = False
# AWS Settings End # AWS Settings End
DEFAULT_FILE_STORAGE = "django_s3_storage.storage.S3Storage"
else:
MEDIA_URL = "/uploads/"
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads")
# Enable Connection Pooling (if desired) # Enable Connection Pooling (if desired)
# DATABASES['default']['ENGINE'] = 'django_postgrespool' # DATABASES['default']['ENGINE'] = 'django_postgrespool'
@ -155,7 +170,6 @@ ALLOWED_HOSTS = [
] ]
DEFAULT_FILE_STORAGE = "django_s3_storage.storage.S3Storage"
# Simplified static file serving. # Simplified static file serving.
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
@ -165,6 +179,17 @@ CSRF_COOKIE_SECURE = True
REDIS_URL = os.environ.get("REDIS_URL") REDIS_URL = os.environ.get("REDIS_URL")
if DOCKERIZED:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_URL,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
else:
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django_redis.cache.RedisCache", "BACKEND": "django_redis.cache.RedisCache",
@ -183,10 +208,4 @@ RQ_QUEUES = {
} }
url = urlparse(os.environ.get("REDIS_URL"))
DOCKERIZED = os.environ.get(
"DOCKERIZED", False
) # Set the variable true if running in docker-compose environment
WEB_URL = os.environ.get("WEB_URL") WEB_URL = os.environ.get("WEB_URL")

6
apps/app/.env.example Normal file
View File

@ -0,0 +1,6 @@
NEXT_PUBLIC_API_BASE_URL = "localhost/"
NEXT_PUBLIC_GOOGLE_CLIENTID="<-- google client id -->"
NEXT_PUBLIC_GITHUB_ID="<-- github client id -->"
NEXT_PUBLIC_SENTRY_DSN="<-- sentry dns -->"
NEXT_PUBLIC_ENABLE_OAUTH=0
NEXT_PUBLIC_ENABLE_SENTRY=0

View File

@ -1 +1,4 @@
module.exports = require("config/.eslintrc"); module.exports = {
root: true,
extends: ["custom"],
};

12
apps/app/Dockerfile.dev Normal file
View File

@ -0,0 +1,12 @@
FROM node:18-alpine
RUN apk add --no-cache libc6-compat
RUN apk update
# Set working directory
WORKDIR /app
COPY . .
RUN yarn global add turbo
RUN yarn install
EXPOSE 3000
CMD ["yarn","dev"]

View File

@ -4,33 +4,14 @@ RUN apk update
# Set working directory # Set working directory
WORKDIR /app WORKDIR /app
RUN apk add curl RUN yarn global add turbo
COPY . .
RUN curl -fsSL "https://github.com/pnpm/pnpm/releases/latest/download/pnpm-linuxstatic-x64" -o /bin/pnpm; chmod +x /bin/pnpm;
ENV PNPM_HOME="pnpm"
ENV PATH="${PATH}:./pnpm"
COPY ./apps ./apps
COPY ./package.json ./package.json
COPY ./.eslintrc.js ./.eslintrc.js
COPY ./turbo.json ./turbo.json
COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml
COPY ./pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm add -g turbo
RUN turbo prune --scope=app --docker RUN turbo prune --scope=app --docker
# Add lockfile and package.json's of isolated subworkspace # Add lockfile and package.json's of isolated subworkspace
FROM node:18-alpine AS installer FROM node:18-alpine AS installer
RUN apk add curl
RUN curl -fsSL "https://github.com/pnpm/pnpm/releases/latest/download/pnpm-linuxstatic-x64" -o /bin/pnpm; chmod +x /bin/pnpm;
ENV PNPM_HOME="pnpm"
ENV PATH="${PATH}:./pnpm"
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
RUN apk update RUN apk update
@ -39,14 +20,14 @@ WORKDIR /app
# First install the dependencies (as they change less often) # First install the dependencies (as they change less often)
COPY .gitignore .gitignore COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ . COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN pnpm install RUN yarn install
# Build the project # Build the project
COPY --from=builder /app/out/full/ . COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json COPY turbo.json turbo.json
RUN pnpm turbo run build --filter=app... RUN yarn turbo run build --filter=app
FROM node:18-alpine AS runner FROM node:18-alpine AS runner
WORKDIR /app WORKDIR /app
@ -62,8 +43,9 @@ COPY --from=installer /app/apps/app/package.json .
# Automatically leverage output traces to reduce image size # Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing # https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone ./ COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone ./
# COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone/node_modules ./apps/app/node_modules
COPY --from=installer --chown=captain:plane /app/apps/app/.next/static ./apps/app/.next/static COPY --from=installer --chown=captain:plane /app/apps/app/.next/static ./apps/app/.next/static
EXPOSE 3000 ENV NEXT_TELEMETRY_DISABLED 1
CMD node apps/app/server.js EXPOSE 3000

View File

@ -12,9 +12,13 @@ const nextConfig = {
], ],
}, },
output: "standalone", output: "standalone",
experimental: {
// this includes files from the monorepo base two directories up
outputFileTracingRoot: path.join(__dirname, "../../"),
},
}; };
if (process.env.NEXT_PUBLIC_SENTRY_DSN) { if (process.env.NEXT_PUBLIC_ENABLE_SENTRY) {
module.exports = withSentryConfig(nextConfig, { silent: true }, { hideSourceMaps: true }); module.exports = withSentryConfig(nextConfig, { silent: true }, { hideSourceMaps: true });
} else { } else {
module.exports = nextConfig; module.exports = nextConfig;

View File

@ -46,12 +46,12 @@
"@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2", "@typescript-eslint/parser": "^5.48.2",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.7",
"config": "workspace:*", "eslint-config-custom": "*",
"eslint": "^8.31.0", "eslint": "^8.31.0",
"eslint-config-next": "12.2.2", "eslint-config-next": "12.2.2",
"postcss": "^8.4.14", "postcss": "^8.4.14",
"tailwindcss": "^3.1.6", "tailwindcss": "^3.1.6",
"tsconfig": "workspace:*", "tsconfig": "*",
"typescript": "4.7.4" "typescript": "4.7.4"
} }
} }

View File

@ -11,7 +11,12 @@ import authenticationService from "services/authentication.service";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
// social button // social button
import { GoogleLoginButton, GithubLoginButton, EmailSignInForm } from "components/account"; import {
GoogleLoginButton,
GithubLoginButton,
EmailSignInForm,
EmailPasswordForm,
} from "components/account";
// ui // ui
import { Spinner } from "components/ui"; import { Spinner } from "components/ui";
// icons // icons
@ -19,8 +24,6 @@ import Logo from "public/logo-with-text.png";
// types // types
import type { NextPage } from "next"; import type { NextPage } from "next";
const { NEXT_PUBLIC_GITHUB_ID } = process.env;
const SignInPage: NextPage = () => { const SignInPage: NextPage = () => {
// router // router
const router = useRouter(); const router = useRouter();
@ -69,7 +72,7 @@ const SignInPage: NextPage = () => {
.socialAuth({ .socialAuth({
medium: "github", medium: "github",
credential, credential,
clientId: NEXT_PUBLIC_GITHUB_ID, clientId: process.env.NEXT_PUBLIC_GITHUB_ID,
}) })
.then(async () => { .then(async () => {
await onSignInSuccess(); await onSignInSuccess();
@ -109,6 +112,8 @@ const SignInPage: NextPage = () => {
Sign in to your account Sign in to your account
</h2> </h2>
<div className="mt-16 bg-white py-8 px-4 sm:rounded-lg sm:px-10"> <div className="mt-16 bg-white py-8 px-4 sm:rounded-lg sm:px-10">
{Boolean(process.env.NEXT_PUBLIC_ENABLE_OAUTH) ? (
<>
<div className="mb-4"> <div className="mb-4">
<EmailSignInForm handleSuccess={onSignInSuccess} /> <EmailSignInForm handleSuccess={onSignInSuccess} />
</div> </div>
@ -118,6 +123,14 @@ const SignInPage: NextPage = () => {
<div className="mb-4"> <div className="mb-4">
<GithubLoginButton handleSignIn={handleGithubSignIn} /> <GithubLoginButton handleSignIn={handleGithubSignIn} />
</div> </div>
</>
) : (
<>
<div className="mb-4">
<EmailPasswordForm onSuccess={onSignInSuccess} />
</div>
</>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,4 @@
const defaultConfig = require("config/postcss.config");
module.exports = { module.exports = {
...defaultConfig,
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},

View File

@ -1,9 +1,4 @@
/** @type {import('tailwindcss').Config} */
const defaultConfig = require("config/tailwind.config");
module.exports = { module.exports = {
...defaultConfig,
content: ["./pages/**/*.tsx", "./components/**/*.tsx", "./layouts/**/*.tsx", "./ui/**/*.tsx"], content: ["./pages/**/*.tsx", "./components/**/*.tsx", "./layouts/**/*.tsx", "./ui/**/*.tsx"],
theme: { theme: {
extend: { extend: {

View File

@ -1,27 +1,3 @@
// {
// "compilerOptions": {
// "baseUrl": ".",
// "target": "es5",
// "lib": ["dom", "dom.iterable", "esnext"],
// "allowJs": true,
// "skipLibCheck": true,
// "strict": true,
// "forceConsistentCasingInFileNames": true,
// "noEmit": true,
// "esModuleInterop": true,
// "module": "esnext",
// "moduleResolution": "node",
// "resolveJsonModule": true,
// "isolatedModules": true,
// "jsx": "preserve",
// "paths": {
// "@styles/*": ["styles/*"],
// },
// "incremental": true
// },
// "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
// "exclude": ["node_modules"]
// }
{ {
"extends": "tsconfig/nextjs.json", "extends": "tsconfig/nextjs.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],

View File

@ -1 +1,4 @@
module.exports = require('config/.eslintrc') module.exports = {
root: true,
extends: ['custom'],
}

View File

@ -38,6 +38,7 @@
"zustand": "^4.1.4" "zustand": "^4.1.4"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.51.0",
"eslint": "8.26.0", "eslint": "8.26.0",
"eslint-config-next": "13.0.2", "eslint-config-next": "13.0.2",
"prettier": "^2.7.1", "prettier": "^2.7.1",

View File

@ -2,6 +2,7 @@ import { Guides } from '@/components/Guides'
import { Resources } from '@/components/Resources' import { Resources } from '@/components/Resources'
import { HeroPattern } from '@/components/HeroPattern' import { HeroPattern } from '@/components/HeroPattern'
import { Heading } from '@/components/Heading' import { Heading } from '@/components/Heading'
import { Button } from '@/components/Button'
export const description = '.' export const description = '.'

View File

@ -1,4 +1,5 @@
import { Heading } from '@/components/Heading' import { Heading } from '@/components/Heading'
import { Note } from '@/components/mdx'
# Project setup # Project setup

View File

@ -1,3 +1,5 @@
import { Note } from '@/components/mdx'
# Get Started # Get Started
This section of the Plane docs helps you get comfortable with the product and find your way around more effectively. This section of the Plane docs helps you get comfortable with the product and find your way around more effectively.

View File

@ -1,6 +1,7 @@
# Self Hosting Plane # Self Hosting Plane
import { Heading } from '@/components/Heading' import { Heading } from '@/components/Heading'
import { Note } from '@/components/mdx'
<Note> <Note>
Plane is still in its early days, not everything will be perfect yet, and Plane is still in its early days, not everything will be perfect yet, and

View File

@ -1,4 +1,5 @@
import { Heading } from '@/components/Heading' import { Heading } from '@/components/Heading'
import { Note } from '@/components/mdx'
# Workspace setup # Workspace setup

View File

@ -1,8 +1,19 @@
version: "3.8" version: "3.8"
services: services:
nginx:
container_name: nginx
build:
context: ./nginx
dockerfile: Dockerfile
ports:
- 80:80
depends_on:
# - plane-web
- plane-api
db: db:
image: postgres:12-alpine image: postgres:12-alpine
container_name: db
restart: always restart: always
volumes: volumes:
- pgdata:/var/lib/postgresql/data - pgdata:/var/lib/postgresql/data
@ -12,31 +23,28 @@ services:
POSTGRES_PASSWORD: plane POSTGRES_PASSWORD: plane
command: postgres -c 'max_connections=1000' command: postgres -c 'max_connections=1000'
ports: ports:
- "5432:5432" - 5432:5432
redis: redis:
image: redis:6.2.7-alpine image: redis:6.2.7-alpine
container_name: redis
restart: always restart: always
ports: ports:
- "6379:6379" - 6379:6379
volumes: volumes:
- redisdata:/data - redisdata:/data
plane-web: plane-web:
image: plane-web container_name: planefrontend
container_name: plane-frontend
build: build:
context: . context: .
dockerfile: ./apps/app/Dockerfile.web dockerfile: ./apps/app/Dockerfile.web
restart: always restart: always
command: node apps/app/server.js
env_file: env_file:
- ./apps/app/.env - ./apps/app/.env
ports: ports:
- 3000:3000 - 3000:3000
plane-api: plane-api:
image: plane-api container_name: planebackend
container_name: plane-backend
build: build:
context: ./apiserver context: ./apiserver
dockerfile: Dockerfile.api dockerfile: Dockerfile.api
@ -45,7 +53,6 @@ services:
- 8000:8000 - 8000:8000
env_file: env_file:
- ./apiserver/.env - ./apiserver/.env
depends_on: depends_on:
- db - db
- redis - redis
@ -53,10 +60,11 @@ services:
links: links:
- db:db - db:db
- redis:redis - redis:redis
plane-worker: plane-worker:
image: plane-api container_name: planerqworker
container_name: plane-rqworker build:
context: ./apiserver
dockerfile: Dockerfile.api
depends_on: depends_on:
- redis - redis
- db - db
@ -67,7 +75,6 @@ services:
- db:db - db:db
env_file: env_file:
- ./apiserver/.env - ./apiserver/.env
volumes: volumes:
pgdata: pgdata:
redisdata: redisdata:

4
nginx/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM nginx:1.21-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

View File

@ -0,0 +1,25 @@
upstream plane {
server localhost:80;
}
error_log /var/log/nginx/error.log;
server {
listen 80;
root /www/data/;
access_log /var/log/nginx/access.log;
location / {
proxy_pass http://localhost:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ {
proxy_pass http://localhost:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

25
nginx/nginx.conf Normal file
View File

@ -0,0 +1,25 @@
upstream plane {
server localhost:80;
}
error_log /var/log/nginx/error.log;
server {
listen 80;
root /www/data/;
access_log /var/log/nginx/access.log;
location / {
proxy_pass http://planefrontend:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ {
proxy_pass http://planebackend:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

24
nginx/supervisor.conf Normal file
View File

@ -0,0 +1,24 @@
[supervisord] ## This is the main process for the Supervisor
nodaemon=true
[program:node]
command=node /app/apps/app/server.js
autostart=true
autorestart=true
stderr_logfile=/var/log/node.err.log
stdout_logfile=/var/log/node.out.log
[program:python]
directory=/code
command=sh bin/takeoff
autostart=true
autorestart=true
stderr_logfile=/var/log/python.err.log
stdout_logfile=/var/log/python.out.log
[program:nginx]
command=nginx -g "daemon off;"
autostart=true
autorestart=true
stderr_logfile=/var/log/nginx.err.log
stdout_logfile=/var/log/nginx.out.log

View File

@ -14,9 +14,9 @@
"clean": "turbo run clean" "clean": "turbo run clean"
}, },
"devDependencies": { "devDependencies": {
"config": "workspace:*", "eslint-config-custom": "*",
"prettier": "latest", "prettier": "latest",
"turbo": "latest" "turbo": "latest"
}, },
"packageManager": "pnpm@7.24.3" "packageManager": "yarn@1.22.19"
} }

View File

@ -1,45 +0,0 @@
module.exports = {
extends: ["next", "turbo", "prettier"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
settings: {
next: {
rootDir: ["apps/*/", "packages/*/"],
},
},
rules: {
"@next/next/no-html-link-for-pages": "off",
"prefer-const": "error",
"no-irregular-whitespace": "error",
"no-trailing-spaces": "error",
"no-duplicate-imports": "error",
"arrow-body-style": ["error", "as-needed"],
"react/self-closing-comp": ["error", { component: true, html: true }],
"import/order": [
"warn",
{
groups: ["external", "parent", "sibling", "index", "object", "type"],
pathGroups: [
{
pattern: "@/**/**",
group: "parent",
position: "before",
},
],
},
],
"no-restricted-imports": [
"error",
{
patterns: ["../"],
},
],
},
};

View File

@ -1,8 +0,0 @@
module.exports = {
extends: ["next", "prettier"],
settings: {
next: {
rootDir: ["apps/*/", "packages/*/"],
},
},
};

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -1,10 +0,0 @@
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};

View File

@ -0,0 +1,20 @@
module.exports = {
extends: ["next", "turbo", "prettier"],
parser: "@typescript-eslint/parser",
plugins: ["react", "@typescript-eslint"],
settings: {
next: {
rootDir: ["app/", "docs/", "packages/*/"],
},
},
rules: {
"@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off",
"prefer-const": "error",
"no-irregular-whitespace": "error",
"no-trailing-spaces": "error",
"no-duplicate-imports": "error",
"arrow-body-style": ["error", "as-needed"],
"react/self-closing-comp": ["error", { component: true, html: true }],
},
};

View File

@ -1,5 +1,5 @@
{ {
"name": "config", "name": "eslint-config-custom",
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.js",
"license": "MIT", "license": "MIT",

View File

@ -0,0 +1,3 @@
export const Button = () => {
return <button>button</button>;
};

View File

@ -14,4 +14,4 @@
// export * from "./spinner"; // export * from "./spinner";
// export * from "./text-area"; // export * from "./text-area";
// export * from "./tooltip"; // export * from "./tooltip";
export {}; export * from "./button";

View File

@ -10,12 +10,13 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.17", "@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"config": "workspace:*",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-custom": "*",
"next": "12.3.2", "next": "12.3.2",
"react": "^18.2.0", "react": "^18.2.0",
"tsconfig": "workspace:*", "tsconfig": "*",
"typescript": "4.7.4" "typescript": "4.7.4"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
packages:
- 'apps/*'
- "packages/*"

4
setup.sh Normal file
View File

@ -0,0 +1,4 @@
# Generating API Server environmental variables
cp ./apiserver/.env.example ./apiserver/.env
# Generating App environmental variables
cp ./apps/app/.env.example ./apps/app/.env

View File

@ -9,7 +9,9 @@
"NEXT_PUBLIC_DOCSEARCH_INDEX_NAME", "NEXT_PUBLIC_DOCSEARCH_INDEX_NAME",
"NEXT_PUBLIC_SENTRY_DSN", "NEXT_PUBLIC_SENTRY_DSN",
"SENTRY_AUTH_TOKEN", "SENTRY_AUTH_TOKEN",
"NEXT_PUBLIC_SENTRY_ENVIRONMENT" "NEXT_PUBLIC_SENTRY_ENVIRONMENT",
"NEXT_PUBLIC_ENABLE_SENTRY",
"NEXT_PUBLIC_ENABLE_OAUTH"
], ],
"pipeline": { "pipeline": {
"build": { "build": {

9095
yarn.lock Normal file

File diff suppressed because it is too large Load Diff