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 = {
|
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/*"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -64,4 +64,9 @@ package-lock.json
|
|||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
# Sentry
|
# Sentry
|
||||||
.sentryclirc
|
.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 -->"
|
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
|
||||||
|
@ -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" ]
|
||||||
|
|
||||||
|
@ -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 -
|
||||||
|
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 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
|
||||||
|
@ -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,15 +46,16 @@ INTERNAL_IPS = ("127.0.0.1",)
|
|||||||
|
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
|
|
||||||
sentry_sdk.init(
|
if os.environ.get("SENTRY_DSN", False):
|
||||||
dsn=os.environ.get("SENTRY_DSN"),
|
sentry_sdk.init(
|
||||||
integrations=[DjangoIntegration(), RedisIntegration()],
|
dsn=os.environ.get("SENTRY_DSN"),
|
||||||
# If you wish to associate users to errors (assuming you are using
|
integrations=[DjangoIntegration(), RedisIntegration()],
|
||||||
# django.contrib.auth) you may enable sending PII data.
|
# If you wish to associate users to errors (assuming you are using
|
||||||
send_default_pii=True,
|
# django.contrib.auth) you may enable sending PII data.
|
||||||
environment="local",
|
send_default_pii=True,
|
||||||
traces_sample_rate=0.7,
|
environment="local",
|
||||||
)
|
traces_sample_rate=0.7,
|
||||||
|
)
|
||||||
|
|
||||||
REDIS_HOST = "localhost"
|
REDIS_HOST = "localhost"
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT = 6379
|
||||||
@ -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()
|
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,99 +52,110 @@ 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(
|
||||||
|
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.
|
||||||
|
traces_sample_rate=1,
|
||||||
|
send_default_pii=True,
|
||||||
|
environment="production",
|
||||||
|
)
|
||||||
|
|
||||||
sentry_sdk.init(
|
if (
|
||||||
dsn=os.environ.get("SENTRY_DSN"),
|
os.environ.get("AWS_REGION", False)
|
||||||
integrations=[DjangoIntegration(), RedisIntegration()],
|
and os.environ.get("AWS_ACCESS_KEY_ID", False)
|
||||||
# If you wish to associate users to errors (assuming you are using
|
and os.environ.get("AWS_SECRET_ACCESS_KEY", False)
|
||||||
# django.contrib.auth) you may enable sending PII data.
|
and os.environ.get("AWS_S3_BUCKET_NAME", False)
|
||||||
traces_sample_rate=1,
|
):
|
||||||
send_default_pii=True,
|
# The AWS region to connect to.
|
||||||
environment="production",
|
AWS_REGION = os.environ.get("AWS_REGION", "")
|
||||||
)
|
|
||||||
|
|
||||||
# The AWS region to connect to.
|
# The AWS access key to use.
|
||||||
AWS_REGION = os.environ.get("AWS_REGION")
|
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "")
|
||||||
|
|
||||||
# The AWS access key to use.
|
# The AWS secret access key to use.
|
||||||
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
|
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
|
||||||
|
|
||||||
# The AWS secret access key to use.
|
# The optional AWS session token to use.
|
||||||
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
|
# AWS_SESSION_TOKEN = ""
|
||||||
|
|
||||||
# The optional AWS session token to use.
|
# The name of the bucket to store files in.
|
||||||
# AWS_SESSION_TOKEN = ""
|
AWS_S3_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "")
|
||||||
|
|
||||||
|
# How to construct S3 URLs ("auto", "path", "virtual").
|
||||||
|
AWS_S3_ADDRESSING_STYLE = "auto"
|
||||||
|
|
||||||
# The name of the bucket to store files in.
|
# The full URL to the S3 endpoint. Leave blank to use the default region URL.
|
||||||
AWS_S3_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME")
|
AWS_S3_ENDPOINT_URL = ""
|
||||||
|
|
||||||
# How to construct S3 URLs ("auto", "path", "virtual").
|
# A prefix to be applied to every stored file. This will be joined to every filename using the "/" separator.
|
||||||
AWS_S3_ADDRESSING_STYLE = "auto"
|
AWS_S3_KEY_PREFIX = ""
|
||||||
|
|
||||||
# The full URL to the S3 endpoint. Leave blank to use the default region URL.
|
# Whether to enable authentication for stored files. If True, then generated URLs will include an authentication
|
||||||
AWS_S3_ENDPOINT_URL = ""
|
# token valid for `AWS_S3_MAX_AGE_SECONDS`. If False, then generated URLs will not include an authentication token,
|
||||||
|
# and their permissions will be set to "public-read".
|
||||||
|
AWS_S3_BUCKET_AUTH = False
|
||||||
|
|
||||||
# A prefix to be applied to every stored file. This will be joined to every filename using the "/" separator.
|
# How long generated URLs are valid for. This affects the expiry of authentication tokens if `AWS_S3_BUCKET_AUTH`
|
||||||
AWS_S3_KEY_PREFIX = ""
|
# is True. It also affects the "Cache-Control" header of the files.
|
||||||
|
# Important: Changing this setting will not affect existing files.
|
||||||
|
AWS_S3_MAX_AGE_SECONDS = 60 * 60 # 1 hours.
|
||||||
|
|
||||||
# Whether to enable authentication for stored files. If True, then generated URLs will include an authentication
|
# A URL prefix to be used for generated URLs. This is useful if your bucket is served through a CDN. This setting
|
||||||
# token valid for `AWS_S3_MAX_AGE_SECONDS`. If False, then generated URLs will not include an authentication token,
|
# cannot be used with `AWS_S3_BUCKET_AUTH`.
|
||||||
# and their permissions will be set to "public-read".
|
AWS_S3_PUBLIC_URL = ""
|
||||||
AWS_S3_BUCKET_AUTH = False
|
|
||||||
|
|
||||||
# How long generated URLs are valid for. This affects the expiry of authentication tokens if `AWS_S3_BUCKET_AUTH`
|
# If True, then files will be stored with reduced redundancy. Check the S3 documentation and make sure you
|
||||||
# is True. It also affects the "Cache-Control" header of the files.
|
# understand the consequences before enabling.
|
||||||
# Important: Changing this setting will not affect existing files.
|
# Important: Changing this setting will not affect existing files.
|
||||||
AWS_S3_MAX_AGE_SECONDS = 60 * 60 # 1 hours.
|
AWS_S3_REDUCED_REDUNDANCY = False
|
||||||
|
|
||||||
# A URL prefix to be used for generated URLs. This is useful if your bucket is served through a CDN. This setting
|
# The Content-Disposition header used when the file is downloaded. This can be a string, or a function taking a
|
||||||
# cannot be used with `AWS_S3_BUCKET_AUTH`.
|
# single `name` argument.
|
||||||
AWS_S3_PUBLIC_URL = ""
|
# Important: Changing this setting will not affect existing files.
|
||||||
|
AWS_S3_CONTENT_DISPOSITION = ""
|
||||||
|
|
||||||
# If True, then files will be stored with reduced redundancy. Check the S3 documentation and make sure you
|
# The Content-Language header used when the file is downloaded. This can be a string, or a function taking a
|
||||||
# understand the consequences before enabling.
|
# single `name` argument.
|
||||||
# Important: Changing this setting will not affect existing files.
|
# Important: Changing this setting will not affect existing files.
|
||||||
AWS_S3_REDUCED_REDUNDANCY = False
|
AWS_S3_CONTENT_LANGUAGE = ""
|
||||||
|
|
||||||
# The Content-Disposition header used when the file is downloaded. This can be a string, or a function taking a
|
# A mapping of custom metadata for each file. Each value can be a string, or a function taking a
|
||||||
# single `name` argument.
|
# single `name` argument.
|
||||||
# Important: Changing this setting will not affect existing files.
|
# Important: Changing this setting will not affect existing files.
|
||||||
AWS_S3_CONTENT_DISPOSITION = ""
|
AWS_S3_METADATA = {}
|
||||||
|
|
||||||
# The Content-Language header used when the file is downloaded. This can be a string, or a function taking a
|
# If True, then files will be stored using AES256 server-side encryption.
|
||||||
# single `name` argument.
|
# If this is a string value (e.g., "aws:kms"), that encryption type will be used.
|
||||||
# Important: Changing this setting will not affect existing files.
|
# Otherwise, server-side encryption is not be enabled.
|
||||||
AWS_S3_CONTENT_LANGUAGE = ""
|
# Important: Changing this setting will not affect existing files.
|
||||||
|
AWS_S3_ENCRYPT_KEY = False
|
||||||
|
|
||||||
# A mapping of custom metadata for each file. Each value can be a string, or a function taking a
|
# The AWS S3 KMS encryption key ID (the `SSEKMSKeyId` parameter) is set from this string if present.
|
||||||
# single `name` argument.
|
# This is only relevant if AWS S3 KMS server-side encryption is enabled (above).
|
||||||
# Important: Changing this setting will not affect existing files.
|
# AWS_S3_KMS_ENCRYPTION_KEY_ID = ""
|
||||||
AWS_S3_METADATA = {}
|
|
||||||
|
|
||||||
# If True, then files will be stored using AES256 server-side encryption.
|
# If True, then text files will be stored using gzip content encoding. Files will only be gzipped if their
|
||||||
# If this is a string value (e.g., "aws:kms"), that encryption type will be used.
|
# compressed size is smaller than their uncompressed size.
|
||||||
# Otherwise, server-side encryption is not be enabled.
|
# Important: Changing this setting will not affect existing files.
|
||||||
# Important: Changing this setting will not affect existing files.
|
AWS_S3_GZIP = True
|
||||||
AWS_S3_ENCRYPT_KEY = False
|
|
||||||
|
|
||||||
# The AWS S3 KMS encryption key ID (the `SSEKMSKeyId` parameter) is set from this string if present.
|
# The signature version to use for S3 requests.
|
||||||
# This is only relevant if AWS S3 KMS server-side encryption is enabled (above).
|
AWS_S3_SIGNATURE_VERSION = None
|
||||||
# AWS_S3_KMS_ENCRYPTION_KEY_ID = ""
|
|
||||||
|
|
||||||
# If True, then text files will be stored using gzip content encoding. Files will only be gzipped if their
|
# If True, then files with the same name will overwrite each other. By default it's set to False to have
|
||||||
# compressed size is smaller than their uncompressed size.
|
# extra characters appended.
|
||||||
# Important: Changing this setting will not affect existing files.
|
AWS_S3_FILE_OVERWRITE = False
|
||||||
AWS_S3_GZIP = True
|
|
||||||
|
|
||||||
# The signature version to use for S3 requests.
|
# AWS Settings End
|
||||||
AWS_S3_SIGNATURE_VERSION = None
|
|
||||||
|
|
||||||
# If True, then files with the same name will overwrite each other. By default it's set to False to have
|
DEFAULT_FILE_STORAGE = "django_s3_storage.storage.S3Storage"
|
||||||
# extra characters appended.
|
|
||||||
AWS_S3_FILE_OVERWRITE = False
|
|
||||||
|
|
||||||
# AWS Settings End
|
else:
|
||||||
|
MEDIA_URL = "/uploads/"
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads")
|
||||||
|
|
||||||
|
|
||||||
# Enable Connection Pooling (if desired)
|
# Enable Connection Pooling (if desired)
|
||||||
@ -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,16 +179,27 @@ CSRF_COOKIE_SECURE = True
|
|||||||
|
|
||||||
REDIS_URL = os.environ.get("REDIS_URL")
|
REDIS_URL = os.environ.get("REDIS_URL")
|
||||||
|
|
||||||
CACHES = {
|
if DOCKERIZED:
|
||||||
"default": {
|
CACHES = {
|
||||||
"BACKEND": "django_redis.cache.RedisCache",
|
"default": {
|
||||||
"LOCATION": REDIS_URL,
|
"BACKEND": "django_redis.cache.RedisCache",
|
||||||
"OPTIONS": {
|
"LOCATION": REDIS_URL,
|
||||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
"OPTIONS": {
|
||||||
"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": False},
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
CACHES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django_redis.cache.RedisCache",
|
||||||
|
"LOCATION": REDIS_URL,
|
||||||
|
"OPTIONS": {
|
||||||
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||||
|
"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": False},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
RQ_QUEUES = {
|
RQ_QUEUES = {
|
||||||
"default": {
|
"default": {
|
||||||
@ -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
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
|
# 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
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,15 +112,25 @@ 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">
|
||||||
<div className="mb-4">
|
{Boolean(process.env.NEXT_PUBLIC_ENABLE_OAUTH) ? (
|
||||||
<EmailSignInForm handleSuccess={onSignInSuccess} />
|
<>
|
||||||
</div>
|
<div className="mb-4">
|
||||||
<div className="mb-4">
|
<EmailSignInForm handleSuccess={onSignInSuccess} />
|
||||||
<GoogleLoginButton handleSignIn={handleGoogleSignIn} />
|
</div>
|
||||||
</div>
|
<div className="mb-4">
|
||||||
<div className="mb-4">
|
<GoogleLoginButton handleSignIn={handleGoogleSignIn} />
|
||||||
<GithubLoginButton handleSignIn={handleGithubSignIn} />
|
</div>
|
||||||
</div>
|
<div className="mb-4">
|
||||||
|
<GithubLoginButton handleSignIn={handleGithubSignIn} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="mb-4">
|
||||||
|
<EmailPasswordForm onSuccess={onSignInSuccess} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
const defaultConfig = require("config/postcss.config");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...defaultConfig,
|
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
|
@ -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: {
|
||||||
|
@ -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"],
|
||||||
|
@ -1 +1,4 @@
|
|||||||
module.exports = require('config/.eslintrc')
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['custom'],
|
||||||
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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 = '.'
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Heading } from '@/components/Heading'
|
import { Heading } from '@/components/Heading'
|
||||||
|
import { Note } from '@/components/mdx'
|
||||||
|
|
||||||
# Project setup
|
# Project setup
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
@ -79,4 +80,4 @@ import { Heading } from '@/components/Heading'
|
|||||||
|
|
||||||
6. Visit
|
6. Visit
|
||||||
1. [http://localhost:3000](http://localhost:3000) - (Frontend)
|
1. [http://localhost:3000](http://localhost:3000) - (Frontend)
|
||||||
2. [http://localhost:8000](http://localhost:8000) - (Backend)
|
2. [http://localhost:8000](http://localhost:8000) - (Backend)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Heading } from '@/components/Heading'
|
import { Heading } from '@/components/Heading'
|
||||||
|
import { Note } from '@/components/mdx'
|
||||||
|
|
||||||
# Workspace setup
|
# Workspace setup
|
||||||
|
|
||||||
|
@ -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
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"
|
"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"
|
||||||
}
|
}
|
||||||
|
@ -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",
|
"version": "0.0.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"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 "./spinner";
|
||||||
// export * from "./text-area";
|
// export * from "./text-area";
|
||||||
// export * from "./tooltip";
|
// export * from "./tooltip";
|
||||||
export {};
|
export * from "./button";
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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_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": {
|
||||||
|
Loading…
Reference in New Issue
Block a user