diff --git a/.env.example b/.env.example index dfe49c0..4199907 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,11 @@ SECRET_KEY= DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] OUTBOUND_PORT=9005 +# Uncomment these variables to automatically create an admin account using these credentials on startup. +# After your first successfull login you can remove these variables from your file for safety reasons. +#ADMIN_EMAIL= +#ADMIN_PASSWORD= + SQL_DATABASE=wygiwyh SQL_USER=wygiwyh SQL_PASSWORD= diff --git a/README.md b/README.md index 60fcceb..05733d6 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ $ nano .env # or any other editor you want to use # Run the app $ docker compose up -d -# Create the first admin account +# Create the first admin account. This isn't required if you set the enviroment variables: ADMIN_EMAIL and ADMIN_PASSWORD. $ docker compose exec -it web python manage.py createsuperuser ``` @@ -129,6 +129,8 @@ To create the first user, open the container's console using Unraid's UI, by cli | ENABLE_SOFT_DELETE | true\|false | false | Whether to enable transactions soft delete, if enabled, deleted transactions will remain in the database. Useful for imports and avoiding duplicate entries. | | KEEP_DELETED_TRANSACTIONS_FOR | int | 365 | Time in days to keep soft deleted transactions for. If 0, will keep all transactions indefinitely. Only works if ENABLE_SOFT_DELETE is true. | | TASK_WORKERS | int | 1 | How many workers to have for async tasks. One should be enough for most use cases | +| ADMIN_EMAIL | string | None | Automatically creates an admin account with this email. Must have `ADMIN_PASSWORD` also set. | +| ADMIN_PASSWORD | string | None | Automatically creates an admin account with this password. Must have `ADMIN_EMAIL` also set. | # How it works diff --git a/app/apps/common/management/commands/setup_users.py b/app/apps/common/management/commands/setup_users.py new file mode 100644 index 0000000..f8abcca --- /dev/null +++ b/app/apps/common/management/commands/setup_users.py @@ -0,0 +1,137 @@ +import os +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth import get_user_model +from django.conf import settings +from django.db import IntegrityError + +# Get the custom User model if defined, otherwise the default User model +User = get_user_model() + + +class Command(BaseCommand): + help = ( + "Creates a superuser from environment variables (ADMIN_EMAIL, ADMIN_PASSWORD) " + "and optionally creates a demo user (demo@demo.com) if settings.DEMO is True." + ) + + def handle(self, *args, **options): + self.stdout.write("Starting user setup...") + + # --- Create Superuser --- + admin_email = os.environ.get("ADMIN_EMAIL") + admin_password = os.environ.get("ADMIN_PASSWORD") + + if admin_email and admin_password: + self.stdout.write(f"Attempting to create superuser: {admin_email}") + # Use email as username for simplicity, requires USERNAME_FIELD='email' + # or adapt if your USERNAME_FIELD is different. + # If USERNAME_FIELD is 'username', you might need ADMIN_USERNAME env var. + username_field = User.USERNAME_FIELD # Get the actual username field name + + # Check if the user already exists by email or username + user_exists_kwargs = {"email": admin_email} + if username_field != "email": + # Assume username should also be the email if not explicitly provided + user_exists_kwargs[username_field] = admin_email + + if User.objects.filter(**user_exists_kwargs).exists(): + self.stdout.write( + self.style.WARNING( + f"Superuser with email '{admin_email}' (or corresponding username) already exists. Skipping creation." + ) + ) + else: + try: + create_kwargs = { + username_field: admin_email, # Use email as username by default + "email": admin_email, + "password": admin_password, + } + User.objects.create_superuser(**create_kwargs) + self.stdout.write( + self.style.SUCCESS( + f"Superuser '{admin_email}' created successfully." + ) + ) + except IntegrityError as e: + self.stdout.write( + self.style.ERROR( + f"Failed to create superuser '{admin_email}'. IntegrityError: {e}" + ) + ) + except Exception as e: + self.stdout.write( + self.style.ERROR( + f"An unexpected error occurred creating superuser '{admin_email}': {e}" + ) + ) + else: + self.stdout.write( + self.style.NOTICE( + "ADMIN_EMAIL or ADMIN_PASSWORD environment variables not set. Skipping superuser creation." + ) + ) + + self.stdout.write("---") # Separator + + # --- Create Demo User --- + # Use getattr to safely check for the DEMO setting, default to False if not present + create_demo_user = getattr(settings, "DEMO", False) + + if create_demo_user: + demo_email = "demo@demo.com" + demo_password = ( + "wygiwyhdemo" # Consider making this an env var too for security + ) + demo_username = demo_email # Using email as username for consistency + + self.stdout.write( + f"DEMO setting is True. Attempting to create demo user: {demo_email}" + ) + + username_field = User.USERNAME_FIELD # Get the actual username field name + + # Check if the user already exists by email or username + user_exists_kwargs = {"email": demo_email} + if username_field != "email": + user_exists_kwargs[username_field] = demo_username + + if User.objects.filter(**user_exists_kwargs).exists(): + self.stdout.write( + self.style.WARNING( + f"Demo user with email '{demo_email}' (or corresponding username) already exists. Skipping creation." + ) + ) + else: + try: + create_kwargs = { + username_field: demo_username, + "email": demo_email, + "password": demo_password, + } + User.objects.create_user(**create_kwargs) + self.stdout.write( + self.style.SUCCESS( + f"Demo user '{demo_email}' created successfully." + ) + ) + except IntegrityError as e: + self.stdout.write( + self.style.ERROR( + f"Failed to create demo user '{demo_email}'. IntegrityError: {e}" + ) + ) + except Exception as e: + self.stdout.write( + self.style.ERROR( + f"An unexpected error occurred creating demo user '{demo_email}': {e}" + ) + ) + else: + self.stdout.write( + self.style.NOTICE( + "DEMO setting is not True (or not set). Skipping demo user creation." + ) + ) + + self.stdout.write(self.style.SUCCESS("User setup command finished.")) diff --git a/docker/dev/django/start b/docker/dev/django/start index dded5aa..8fe851c 100644 --- a/docker/dev/django/start +++ b/docker/dev/django/start @@ -11,4 +11,6 @@ python manage.py migrate # Create flag file to signal migrations are complete touch /tmp/migrations_complete +python manage.py setup_users + exec python manage.py runserver 0.0.0.0:8000 diff --git a/docker/prod/django/start b/docker/prod/django/start index f581619..ffb47a8 100644 --- a/docker/prod/django/start +++ b/docker/prod/django/start @@ -13,4 +13,6 @@ python manage.py migrate # Create flag file to signal migrations are complete touch /tmp/migrations_complete +python manage.py setup_users + exec gunicorn WYGIWYH.wsgi:application --bind 0.0.0.0:8000 --timeout 600