forked from github/plane
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:
parent
33e2986062
commit
bdca84bd09
@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
// This tells ESLint to load the config from the package `config`
|
||||
// extends: ["custom"],
|
||||
// This tells ESLint to load the config from the package `eslint-config-custom`
|
||||
extends: ["custom"],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ["apps/*/"],
|
||||
rootDir: ["apps/*"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -65,3 +65,8 @@ package-lock.json
|
||||
|
||||
# Sentry
|
||||
.sentryclirc
|
||||
|
||||
# lock files
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
116
Dockerfile
Normal file
116
Dockerfile
Normal 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"]
|
||||
|
||||
|
||||
|
||||
|
@ -1,18 +1,22 @@
|
||||
# Backend
|
||||
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_USER="<-- email host user -->"
|
||||
EMAIL_HOST_PASSWORD="<-- email host password -->"
|
||||
|
||||
# AWS
|
||||
AWS_REGION="<-- aws region -->"
|
||||
AWS_ACCESS_KEY_ID="<-- aws access key -->"
|
||||
AWS_SECRET_ACCESS_KEY="<-- aws secret acess key -->"
|
||||
AWS_S3_BUCKET_NAME="<-- aws s3 bucket name -->"
|
||||
|
||||
SENTRY_DSN="<-- sentry dsn -->"
|
||||
WEB_URL="<-- frontend web url -->"
|
||||
|
||||
# FE
|
||||
WEB_URL="localhost/"
|
||||
# OAUTH
|
||||
GITHUB_CLIENT_SECRET="<-- github secret -->"
|
||||
|
||||
# Flags
|
||||
DISABLE_COLLECTSTATIC=1
|
||||
DOCKERIZED=0 //True if running docker compose else 0
|
||||
DOCKERIZED=1
|
||||
|
@ -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
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
@ -8,19 +8,19 @@ ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
WORKDIR /code
|
||||
|
||||
RUN apk --update --no-cache add \
|
||||
"libpq~=14" \
|
||||
"libpq~=15" \
|
||||
"libxslt~=1.1" \
|
||||
"nodejs-current~=18" \
|
||||
"nodejs-current~=19" \
|
||||
"xmlsec~=1.2"
|
||||
|
||||
COPY requirements.txt ./
|
||||
COPY requirements ./requirements
|
||||
RUN apk add libffi-dev
|
||||
RUN apk --update --no-cache --virtual .build-deps add \
|
||||
"bash~=5.1" \
|
||||
"g++~=11.2" \
|
||||
"gcc~=11.2" \
|
||||
"cargo~=1.60" \
|
||||
"bash~=5.2" \
|
||||
"g++~=12.2" \
|
||||
"gcc~=12.2" \
|
||||
"cargo~=1.64" \
|
||||
"git~=2" \
|
||||
"make~=4.3" \
|
||||
"postgresql13-dev~=13" \
|
||||
@ -46,15 +46,16 @@ COPY templates templates/
|
||||
|
||||
COPY gunicorn.config.py ./
|
||||
USER root
|
||||
RUN apk --update --no-cache add "bash~=5.1"
|
||||
RUN apk --update --no-cache add "bash~=5.2"
|
||||
COPY ./bin ./bin/
|
||||
|
||||
RUN chmod +x ./bin/takeoff ./bin/worker
|
||||
RUN chmod -R 777 /code
|
||||
|
||||
USER captain
|
||||
|
||||
# Expose container port and run entry point script
|
||||
EXPOSE 8000
|
||||
|
||||
CMD [ "./bin/takeoff" ]
|
||||
# CMD [ "./bin/takeoff" ]
|
||||
|
||||
|
@ -2,4 +2,8 @@
|
||||
set -e
|
||||
python manage.py wait_for_db
|
||||
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 -
|
||||
|
28
apiserver/bin/user_script.py
Normal file
28
apiserver/bin/user_script.py
Normal 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()
|
@ -1,12 +1,13 @@
|
||||
import os
|
||||
import datetime
|
||||
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__)))
|
||||
|
||||
|
||||
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!
|
||||
DEBUG = True
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import dj_database_url
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
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 = {
|
||||
"default": {
|
||||
@ -41,6 +46,7 @@ INTERNAL_IPS = ("127.0.0.1",)
|
||||
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
|
||||
if os.environ.get("SENTRY_DSN", False):
|
||||
sentry_sdk.init(
|
||||
dsn=os.environ.get("SENTRY_DSN"),
|
||||
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")
|
||||
|
@ -33,6 +33,10 @@ CORS_ORIGIN_WHITELIST = [
|
||||
DATABASES["default"] = dj_database_url.config()
|
||||
SITE_ID = 1
|
||||
|
||||
DOCKERIZED = os.environ.get(
|
||||
"DOCKERIZED", False
|
||||
) # Set the variable true if running in docker-compose environment
|
||||
|
||||
# Enable Connection Pooling (if desired)
|
||||
# DATABASES['default']['ENGINE'] = 'django_postgrespool'
|
||||
|
||||
@ -48,9 +52,9 @@ CORS_ALLOW_ALL_ORIGINS = True
|
||||
# Simplified static file serving.
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
|
||||
|
||||
if os.environ.get("SENTRY_DSN", False):
|
||||
sentry_sdk.init(
|
||||
dsn=os.environ.get("SENTRY_DSN"),
|
||||
dsn=os.environ.get("SENTRY_DSN", ""),
|
||||
integrations=[DjangoIntegration(), RedisIntegration()],
|
||||
# If you wish to associate users to errors (assuming you are using
|
||||
# django.contrib.auth) you may enable sending PII data.
|
||||
@ -59,21 +63,26 @@ sentry_sdk.init(
|
||||
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.
|
||||
AWS_REGION = os.environ.get("AWS_REGION")
|
||||
AWS_REGION = os.environ.get("AWS_REGION", "")
|
||||
|
||||
# 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.
|
||||
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.
|
||||
# AWS_SESSION_TOKEN = ""
|
||||
|
||||
|
||||
# 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").
|
||||
AWS_S3_ADDRESSING_STYLE = "auto"
|
||||
@ -142,6 +151,12 @@ AWS_S3_FILE_OVERWRITE = False
|
||||
|
||||
# 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)
|
||||
# DATABASES['default']['ENGINE'] = 'django_postgrespool'
|
||||
@ -155,7 +170,6 @@ ALLOWED_HOSTS = [
|
||||
]
|
||||
|
||||
|
||||
DEFAULT_FILE_STORAGE = "django_s3_storage.storage.S3Storage"
|
||||
# Simplified static file serving.
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
|
||||
@ -165,6 +179,17 @@ CSRF_COOKIE_SECURE = True
|
||||
|
||||
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 = {
|
||||
"default": {
|
||||
"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")
|
||||
|
6
apps/app/.env.example
Normal file
6
apps/app/.env.example
Normal 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
|
@ -1 +1,4 @@
|
||||
module.exports = require("config/.eslintrc");
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["custom"],
|
||||
};
|
||||
|
12
apps/app/Dockerfile.dev
Normal file
12
apps/app/Dockerfile.dev
Normal 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"]
|
@ -4,33 +4,14 @@ RUN apk update
|
||||
# Set working directory
|
||||
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
|
||||
|
||||
# Add lockfile and package.json's of isolated subworkspace
|
||||
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 update
|
||||
@ -39,14 +20,14 @@ 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/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN pnpm install
|
||||
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 pnpm turbo run build --filter=app...
|
||||
RUN yarn turbo run build --filter=app
|
||||
|
||||
FROM node:18-alpine AS runner
|
||||
WORKDIR /app
|
||||
@ -62,8 +43,9 @@ COPY --from=installer /app/apps/app/package.json .
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# 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/node_modules ./apps/app/node_modules
|
||||
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
|
||||
|
@ -12,9 +12,13 @@ const nextConfig = {
|
||||
],
|
||||
},
|
||||
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 });
|
||||
} else {
|
||||
module.exports = nextConfig;
|
||||
|
@ -46,12 +46,12 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"config": "workspace:*",
|
||||
"eslint-config-custom": "*",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-next": "12.2.2",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"tsconfig": "workspace:*",
|
||||
"tsconfig": "*",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,12 @@ import authenticationService from "services/authentication.service";
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// social button
|
||||
import { GoogleLoginButton, GithubLoginButton, EmailSignInForm } from "components/account";
|
||||
import {
|
||||
GoogleLoginButton,
|
||||
GithubLoginButton,
|
||||
EmailSignInForm,
|
||||
EmailPasswordForm,
|
||||
} from "components/account";
|
||||
// ui
|
||||
import { Spinner } from "components/ui";
|
||||
// icons
|
||||
@ -19,8 +24,6 @@ import Logo from "public/logo-with-text.png";
|
||||
// types
|
||||
import type { NextPage } from "next";
|
||||
|
||||
const { NEXT_PUBLIC_GITHUB_ID } = process.env;
|
||||
|
||||
const SignInPage: NextPage = () => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
@ -69,7 +72,7 @@ const SignInPage: NextPage = () => {
|
||||
.socialAuth({
|
||||
medium: "github",
|
||||
credential,
|
||||
clientId: NEXT_PUBLIC_GITHUB_ID,
|
||||
clientId: process.env.NEXT_PUBLIC_GITHUB_ID,
|
||||
})
|
||||
.then(async () => {
|
||||
await onSignInSuccess();
|
||||
@ -109,6 +112,8 @@ const SignInPage: NextPage = () => {
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<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">
|
||||
<EmailSignInForm handleSuccess={onSignInSuccess} />
|
||||
</div>
|
||||
@ -118,6 +123,14 @@ const SignInPage: NextPage = () => {
|
||||
<div className="mb-4">
|
||||
<GithubLoginButton handleSignIn={handleGithubSignIn} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<EmailPasswordForm onSuccess={onSignInSuccess} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,4 @@
|
||||
const defaultConfig = require("config/postcss.config");
|
||||
|
||||
module.exports = {
|
||||
...defaultConfig,
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
|
@ -1,9 +1,4 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
const defaultConfig = require("config/tailwind.config");
|
||||
|
||||
module.exports = {
|
||||
...defaultConfig,
|
||||
content: ["./pages/**/*.tsx", "./components/**/*.tsx", "./layouts/**/*.tsx", "./ui/**/*.tsx"],
|
||||
theme: {
|
||||
extend: {
|
||||
|
@ -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",
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
|
@ -1 +1,4 @@
|
||||
module.exports = require('config/.eslintrc')
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom'],
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
"zustand": "^4.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"eslint": "8.26.0",
|
||||
"eslint-config-next": "13.0.2",
|
||||
"prettier": "^2.7.1",
|
||||
|
@ -2,6 +2,7 @@ import { Guides } from '@/components/Guides'
|
||||
import { Resources } from '@/components/Resources'
|
||||
import { HeroPattern } from '@/components/HeroPattern'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { Button } from '@/components/Button'
|
||||
|
||||
export const description = '.'
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { Note } from '@/components/mdx'
|
||||
|
||||
# Project setup
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Note } from '@/components/mdx'
|
||||
|
||||
# Get Started
|
||||
|
||||
This section of the Plane docs helps you get comfortable with the product and find your way around more effectively.
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Self Hosting Plane
|
||||
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { Note } from '@/components/mdx'
|
||||
|
||||
<Note>
|
||||
Plane is still in its early days, not everything will be perfect yet, and
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { Note } from '@/components/mdx'
|
||||
|
||||
# Workspace setup
|
||||
|
||||
|
@ -1,8 +1,19 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
nginx:
|
||||
container_name: nginx
|
||||
build:
|
||||
context: ./nginx
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- 80:80
|
||||
depends_on:
|
||||
# - plane-web
|
||||
- plane-api
|
||||
db:
|
||||
image: postgres:12-alpine
|
||||
container_name: db
|
||||
restart: always
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
@ -12,31 +23,28 @@ services:
|
||||
POSTGRES_PASSWORD: plane
|
||||
command: postgres -c 'max_connections=1000'
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
- 5432:5432
|
||||
redis:
|
||||
image: redis:6.2.7-alpine
|
||||
container_name: redis
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
- 6379:6379
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
plane-web:
|
||||
image: plane-web
|
||||
container_name: plane-frontend
|
||||
container_name: planefrontend
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/app/Dockerfile.web
|
||||
restart: always
|
||||
command: node apps/app/server.js
|
||||
env_file:
|
||||
- ./apps/app/.env
|
||||
ports:
|
||||
- 3000:3000
|
||||
|
||||
plane-api:
|
||||
image: plane-api
|
||||
container_name: plane-backend
|
||||
container_name: planebackend
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.api
|
||||
@ -45,7 +53,6 @@ services:
|
||||
- 8000:8000
|
||||
env_file:
|
||||
- ./apiserver/.env
|
||||
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
@ -53,10 +60,11 @@ services:
|
||||
links:
|
||||
- db:db
|
||||
- redis:redis
|
||||
|
||||
plane-worker:
|
||||
image: plane-api
|
||||
container_name: plane-rqworker
|
||||
container_name: planerqworker
|
||||
build:
|
||||
context: ./apiserver
|
||||
dockerfile: Dockerfile.api
|
||||
depends_on:
|
||||
- redis
|
||||
- db
|
||||
@ -67,7 +75,6 @@ services:
|
||||
- db:db
|
||||
env_file:
|
||||
- ./apiserver/.env
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
redisdata:
|
||||
|
4
nginx/Dockerfile
Normal file
4
nginx/Dockerfile
Normal 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
|
25
nginx/nginx-single-docker-image.conf
Normal file
25
nginx/nginx-single-docker-image.conf
Normal 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
25
nginx/nginx.conf
Normal 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
24
nginx/supervisor.conf
Normal 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
|
@ -14,9 +14,9 @@
|
||||
"clean": "turbo run clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
"config": "workspace:*",
|
||||
"eslint-config-custom": "*",
|
||||
"prettier": "latest",
|
||||
"turbo": "latest"
|
||||
},
|
||||
"packageManager": "pnpm@7.24.3"
|
||||
"packageManager": "yarn@1.22.19"
|
||||
}
|
||||
|
@ -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: ["../"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ["next", "prettier"],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ["apps/*/", "packages/*/"],
|
||||
},
|
||||
},
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
module.exports = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
20
packages/eslint-config-custom/index.js
Normal file
20
packages/eslint-config-custom/index.js
Normal 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 }],
|
||||
},
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "config",
|
||||
"name": "eslint-config-custom",
|
||||
"version": "0.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
3
packages/ui/button/index.tsx
Normal file
3
packages/ui/button/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export const Button = () => {
|
||||
return <button>button</button>;
|
||||
};
|
@ -14,4 +14,4 @@
|
||||
// export * from "./spinner";
|
||||
// export * from "./text-area";
|
||||
// export * from "./tooltip";
|
||||
export {};
|
||||
export * from "./button";
|
||||
|
@ -10,12 +10,13 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"classnames": "^2.3.2",
|
||||
"config": "workspace:*",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-custom": "*",
|
||||
"next": "12.3.2",
|
||||
"react": "^18.2.0",
|
||||
"tsconfig": "workspace:*",
|
||||
"tsconfig": "*",
|
||||
"typescript": "4.7.4"
|
||||
}
|
||||
}
|
||||
|
10730
pnpm-lock.yaml
10730
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
||||
packages:
|
||||
- 'apps/*'
|
||||
- "packages/*"
|
4
setup.sh
Normal file
4
setup.sh
Normal 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
|
@ -9,7 +9,9 @@
|
||||
"NEXT_PUBLIC_DOCSEARCH_INDEX_NAME",
|
||||
"NEXT_PUBLIC_SENTRY_DSN",
|
||||
"SENTRY_AUTH_TOKEN",
|
||||
"NEXT_PUBLIC_SENTRY_ENVIRONMENT"
|
||||
"NEXT_PUBLIC_SENTRY_ENVIRONMENT",
|
||||
"NEXT_PUBLIC_ENABLE_SENTRY",
|
||||
"NEXT_PUBLIC_ENABLE_OAUTH"
|
||||
],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
|
Loading…
Reference in New Issue
Block a user