forked from github/plane
Merge branch 'feat/self_hosted_instance' of github.com:makeplane/plane into feat/self_hosted_instance
This commit is contained in:
commit
c4d5ccdae4
@ -1,49 +0,0 @@
|
||||
import os, sys
|
||||
|
||||
|
||||
sys.path.append("/code")
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
|
||||
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
|
||||
|
||||
def load_config():
|
||||
from plane.license.models import InstanceConfiguration
|
||||
|
||||
config_keys = {
|
||||
# Authentication Settings
|
||||
"GOOGLE_CLIENT_ID": os.environ.get("GOOGLE_CLIENT_ID"),
|
||||
"GITHUB_CLIENT_ID": os.environ.get("GITHUB_CLIENT_ID"),
|
||||
"GITHUB_CLIENT_SECRET": os.environ.get("GITHUB_CLIENT_SECRET"),
|
||||
"ENABLE_SIGNUP": os.environ.get("ENABLE_SIGNUP", "1"),
|
||||
"ENABLE_EMAIL_PASSWORD": os.environ.get("ENABLE_EMAIL_PASSWORD", "1"),
|
||||
"ENABLE_MAGIC_LINK_LOGIN": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "0"),
|
||||
# Email Settings
|
||||
"EMAIL_HOST": os.environ.get("EMAIL_HOST", ""),
|
||||
"EMAIL_HOST_USER": os.environ.get("EMAIL_HOST_USER", ""),
|
||||
"EMAIL_HOST_PASSWORD": os.environ.get("EMAIL_HOST_PASSWORD"),
|
||||
"EMAIL_PORT": os.environ.get("EMAIL_PORT", "587"),
|
||||
"EMAIL_FROM": os.environ.get("EMAIL_FROM", ""),
|
||||
"EMAIL_USE_TLS": os.environ.get("EMAIL_USE_TLS", "1"),
|
||||
"EMAIL_USE_SSL": os.environ.get("EMAIL_USE_SSL", "0"),
|
||||
# Open AI Settings
|
||||
"OPENAI_API_BASE": os.environ.get("", "https://api.openai.com/v1"),
|
||||
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", "sk-"),
|
||||
"GPT_ENGINE": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"),
|
||||
}
|
||||
|
||||
for key, value in config_keys.items():
|
||||
obj, created = InstanceConfiguration.objects.get_or_create(
|
||||
key=key
|
||||
)
|
||||
obj.value = value
|
||||
obj.save()
|
||||
|
||||
print(f"{key} loaded with value from environment variable.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
load_config()
|
@ -4,9 +4,9 @@ python manage.py wait_for_db
|
||||
python manage.py migrate
|
||||
|
||||
# Register instance
|
||||
python bin/instance_registration.py
|
||||
python manage.py register_instance
|
||||
# Load the configuration variable
|
||||
python bin/instance_configuration.py
|
||||
python manage.py configure_instance
|
||||
# Create the default bucket
|
||||
python bin/bucket_script.py
|
||||
|
||||
|
71
apiserver/plane/db/management/commands/create_bucket.py
Normal file
71
apiserver/plane/db/management/commands/create_bucket.py
Normal file
@ -0,0 +1,71 @@
|
||||
# Python imports
|
||||
import boto3
|
||||
import json
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
# Django imports
|
||||
from django.core.management import BaseCommand
|
||||
from django.conf import settings
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Create the default bucket for the instance"
|
||||
|
||||
def set_bucket_public_policy(self, s3_client, bucket_name):
|
||||
public_policy = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Effect": "Allow",
|
||||
"Principal": "*",
|
||||
"Action": ["s3:GetObject"],
|
||||
"Resource": [f"arn:aws:s3:::{bucket_name}/*"]
|
||||
}]
|
||||
}
|
||||
|
||||
try:
|
||||
s3_client.put_bucket_policy(
|
||||
Bucket=bucket_name,
|
||||
Policy=json.dumps(public_policy)
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(f"Public read access policy set for bucket '{bucket_name}'."))
|
||||
except ClientError as e:
|
||||
self.stdout.write(self.style.ERROR(f"Error setting public read access policy: {e}"))
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# Create a session using the credentials from Django settings
|
||||
try:
|
||||
session = boto3.session.Session(
|
||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||
)
|
||||
# Create an S3 client using the session
|
||||
s3_client = session.client('s3', endpoint_url=settings.AWS_S3_ENDPOINT_URL)
|
||||
bucket_name = settings.AWS_STORAGE_BUCKET_NAME
|
||||
|
||||
self.stdout.write(self.style.NOTICE("Checking bucket..."))
|
||||
|
||||
# Check if the bucket exists
|
||||
s3_client.head_bucket(Bucket=bucket_name)
|
||||
|
||||
self.set_bucket_public_policy(s3_client, bucket_name)
|
||||
except ClientError as e:
|
||||
error_code = int(e.response['Error']['Code'])
|
||||
bucket_name = settings.AWS_STORAGE_BUCKET_NAME
|
||||
if error_code == 404:
|
||||
# Bucket does not exist, create it
|
||||
self.stdout.write(self.style.WARNING(f"Bucket '{bucket_name}' does not exist. Creating bucket..."))
|
||||
try:
|
||||
s3_client.create_bucket(Bucket=bucket_name)
|
||||
self.stdout.write(self.style.SUCCESS(f"Bucket '{bucket_name}' created successfully."))
|
||||
self.set_bucket_public_policy(s3_client, bucket_name)
|
||||
except ClientError as create_error:
|
||||
self.stdout.write(self.style.ERROR(f"Failed to create bucket: {create_error}"))
|
||||
elif error_code == 403:
|
||||
# Access to the bucket is forbidden
|
||||
self.stdout.write(self.style.ERROR(f"Access to the bucket '{bucket_name}' is forbidden. Check permissions."))
|
||||
else:
|
||||
# Another ClientError occurred
|
||||
self.stdout.write(self.style.ERROR(f"Failed to check bucket: {e}"))
|
||||
except Exception as ex:
|
||||
# Handle any other exception
|
||||
self.stdout.write(self.style.ERROR(f"An error occurred: {ex}"))
|
0
apiserver/plane/license/management/__init__.py
Normal file
0
apiserver/plane/license/management/__init__.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Python imports
|
||||
import os
|
||||
|
||||
# Django imports
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import timezone
|
||||
|
||||
# Module imports
|
||||
from plane.license.models import InstanceConfiguration
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Configure instance variables"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
config_keys = {
|
||||
# Authentication Settings
|
||||
"GOOGLE_CLIENT_ID": os.environ.get("GOOGLE_CLIENT_ID"),
|
||||
"GITHUB_CLIENT_ID": os.environ.get("GITHUB_CLIENT_ID"),
|
||||
"GITHUB_CLIENT_SECRET": os.environ.get("GITHUB_CLIENT_SECRET"),
|
||||
"ENABLE_SIGNUP": os.environ.get("ENABLE_SIGNUP", "1"),
|
||||
"ENABLE_EMAIL_PASSWORD": os.environ.get("ENABLE_EMAIL_PASSWORD", "1"),
|
||||
"ENABLE_MAGIC_LINK_LOGIN": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "0"),
|
||||
# Email Settings
|
||||
"EMAIL_HOST": os.environ.get("EMAIL_HOST", ""),
|
||||
"EMAIL_HOST_USER": os.environ.get("EMAIL_HOST_USER", ""),
|
||||
"EMAIL_HOST_PASSWORD": os.environ.get("EMAIL_HOST_PASSWORD"),
|
||||
"EMAIL_PORT": os.environ.get("EMAIL_PORT", "587"),
|
||||
"EMAIL_FROM": os.environ.get("EMAIL_FROM", ""),
|
||||
"EMAIL_USE_TLS": os.environ.get("EMAIL_USE_TLS", "1"),
|
||||
"EMAIL_USE_SSL": os.environ.get("EMAIL_USE_SSL", "0"),
|
||||
# Open AI Settings
|
||||
"OPENAI_API_BASE": os.environ.get("", "https://api.openai.com/v1"),
|
||||
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", "sk-"),
|
||||
"GPT_ENGINE": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"),
|
||||
}
|
||||
|
||||
for key, value in config_keys.items():
|
||||
obj, created = InstanceConfiguration.objects.get_or_create(
|
||||
key=key
|
||||
)
|
||||
if created:
|
||||
obj.value = value
|
||||
obj.save()
|
||||
self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable."))
|
||||
else:
|
||||
self.stdout.write(self.style.WARNING(f"{key} configuration already exists"))
|
@ -1,41 +1,43 @@
|
||||
# Python imports
|
||||
import os, sys
|
||||
import json
|
||||
import uuid
|
||||
import os
|
||||
import requests
|
||||
import uuid
|
||||
|
||||
# Django imports
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import timezone
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
from plane.license.models import Instance, InstanceAdmin
|
||||
|
||||
|
||||
sys.path.append("/code")
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
|
||||
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
|
||||
|
||||
def instance_registration():
|
||||
try:
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
from plane.license.models import Instance, InstanceAdmin
|
||||
class Command(BaseCommand):
|
||||
help = "Check if instance in registered else register"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# Check if the instance is registered
|
||||
instance = Instance.objects.first()
|
||||
|
||||
# If instance is None then register this instance
|
||||
if instance is None:
|
||||
with open("/code/package.json", "r") as file:
|
||||
with open("package.json", "r") as file:
|
||||
# Load JSON content from the file
|
||||
data = json.load(file)
|
||||
|
||||
admin_email = os.environ.get("ADMIN_EMAIL")
|
||||
|
||||
try:
|
||||
validate_email(admin_email)
|
||||
except ValidationError:
|
||||
CommandError(f"{admin_email} is not a valid ADMIN_EMAIL")
|
||||
|
||||
# Raise an exception if the admin email is not provided
|
||||
if not admin_email:
|
||||
raise Exception("ADMIN_EMAIL is required")
|
||||
raise CommandError("ADMIN_EMAIL is required")
|
||||
|
||||
# Check if the admin email user exists
|
||||
user = User.objects.filter(email=admin_email).first()
|
||||
@ -49,7 +51,7 @@ def instance_registration():
|
||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
||||
|
||||
if not license_engine_base_url:
|
||||
raise Exception("LICENSE_ENGINE_BASE_URL is required")
|
||||
raise CommandError("LICENSE_ENGINE_BASE_URL is required")
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
@ -84,21 +86,19 @@ def instance_registration():
|
||||
role=20,
|
||||
)
|
||||
|
||||
print(
|
||||
f"Instance succesfully registered with owner: {instance.primary_owner.email}"
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Instance succesfully registered with owner: {instance.primary_owner.email}"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
print("Instance could not be registered")
|
||||
self.stdout.write(self.style.WARNING("Instance could not be registered"))
|
||||
return
|
||||
else:
|
||||
print(
|
||||
f"Instance already registered with instance owner: {instance.primary_owner.email}"
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Instance already registered with instance owner: {instance.primary_owner.email}"
|
||||
)
|
||||
)
|
||||
return
|
||||
except ImportError:
|
||||
raise ImportError
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
instance_registration()
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.2.5 on 2023-11-13 14:31
|
||||
# Generated by Django 4.2.5 on 2023-11-15 14:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
@ -15,6 +15,34 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Instance',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('instance_name', models.CharField(max_length=255)),
|
||||
('whitelist_emails', models.TextField(blank=True, null=True)),
|
||||
('instance_id', models.CharField(max_length=25, unique=True)),
|
||||
('license_key', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('api_key', models.CharField(max_length=16)),
|
||||
('version', models.CharField(max_length=10)),
|
||||
('primary_email', models.CharField(max_length=256)),
|
||||
('last_checked_at', models.DateTimeField()),
|
||||
('namespace', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('is_telemetry_enabled', models.BooleanField(default=True)),
|
||||
('is_support_required', models.BooleanField(default=True)),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||
('primary_owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_primary_owner', to=settings.AUTH_USER_MODEL)),
|
||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Instance',
|
||||
'verbose_name_plural': 'Instances',
|
||||
'db_table': 'instances',
|
||||
'ordering': ('-created_at',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InstanceConfiguration',
|
||||
fields=[
|
||||
@ -34,30 +62,21 @@ class Migration(migrations.Migration):
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Instance',
|
||||
name='InstanceAdmin',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('instance_name', models.CharField(max_length=255)),
|
||||
('whitelist_emails', models.TextField(blank=True, null=True)),
|
||||
('instance_id', models.CharField(max_length=25, unique=True)),
|
||||
('license_key', models.CharField(blank=True, max_length=256, null=True)),
|
||||
('api_key', models.CharField(max_length=16)),
|
||||
('version', models.CharField(max_length=10)),
|
||||
('email', models.CharField(max_length=256)),
|
||||
('last_checked_at', models.DateTimeField()),
|
||||
('namespace', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('is_telemetry_enabled', models.BooleanField(default=True)),
|
||||
('is_support_required', models.BooleanField(default=True)),
|
||||
('role', models.PositiveIntegerField(choices=[(20, 'Owner'), (15, 'Admin')], default=15)),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
|
||||
('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admins', to='license.instance')),
|
||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Instance',
|
||||
'verbose_name_plural': 'Instances',
|
||||
'db_table': 'instances',
|
||||
'verbose_name': 'Instance Admin',
|
||||
'verbose_name_plural': 'Instance Admins',
|
||||
'db_table': 'instance_admins',
|
||||
'ordering': ('-created_at',),
|
||||
},
|
||||
),
|
||||
|
@ -1,50 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-11-14 10:14
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('license', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='instance',
|
||||
old_name='email',
|
||||
new_name='primary_email',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='instance',
|
||||
name='owner',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='instance',
|
||||
name='primary_owner',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_primary_owner', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InstanceAdmin',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||
('role', models.PositiveIntegerField(choices=[(20, 'Owner'), (15, 'Admin')], default=15)),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||
('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admins', to='license.instance')),
|
||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Instance Admin',
|
||||
'verbose_name_plural': 'Instance Admins',
|
||||
'db_table': 'instance_admins',
|
||||
'ordering': ('-created_at',),
|
||||
},
|
||||
),
|
||||
]
|
Loading…
Reference in New Issue
Block a user