From 16df43309340452a47d89dc6af4d0d64d6ea4b7f Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Wed, 10 Apr 2024 09:48:48 +0530 Subject: [PATCH 01/16] added script --- deploy.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 deploy.py diff --git a/deploy.py b/deploy.py new file mode 100644 index 000000000..09408e511 --- /dev/null +++ b/deploy.py @@ -0,0 +1,117 @@ +import click +import json +import subprocess + +mapping = { + "Default Setup": { + "file": "default.yml", + "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", + "parameters": { + "DB_NAME": { + "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "default": "postgres", + }, + "DB_USER": { + "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "default": "postgres", + }, + "DB_PASSWORD": { + "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "default": "postgres", + }, + "DB_HOST": { + "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "default": "db", + }, + "SECRET_KEY": { + "help": "Django secret key", + "default": "abcd1234", + }, + "AZURE_CONNECTION_STRING": { + "help": "AZURE storage string", + "default": "", + }, + "LOGS_CONTAINER_NAME": { + "help": "Logs container name", + "default": "logs", + }, + }, + }, +} + + +@click.command() +def run_application(): + click.echo("Welcome to the application setup CLI!") + # click.echo("Default configuration includes Django, Celery, and Redis.") + subprocess.run(["docker", "network", "create", "shoonya_backend"], check=True) + selected_components = [] + parameters_dict = {} + + # Ask user if they want PostgreSQL installation + install_postgres = click.prompt( + "Do you want to include PostgreSQL installation? (Y/N)", default="N" + ) + if install_postgres.upper() == "Y": + subprocess.run( + ["docker-compose", "-f", "postgres.yml", "up", "--build", "-d"], check=True + ) + + production = click.prompt( + "Do you want to run the application in production mode? (Y/N)", default="N" + ) + if production.upper() == "N": + click.echo("Running in production mode") + parameters_dict["ENVIRONMENT"] = dict({ + "ENV" : "dev" + }) + for key, value in mapping.items(): + choice = click.prompt( + f"Do you want to include {key}? ({value['description']}) (Y/N)", default="N" + ) + if choice.upper() == "Y": + selected_components.append(key) + parameters = value.get("parameters") + if parameters: + click.echo(f"Please provide values for parameters for {key}:") + component_params = {} + for param, details in parameters.items(): + help_message = details.get("help", "") + default_value = details.get("default", "") + value = click.prompt( + f"Enter value for {param} ({help_message})", + default=default_value, + ) + if not value: + value = default_value + component_params[param] = value + parameters_dict[key] = component_params + + if parameters_dict: + with open("backend/.env", "w") as env_file: + for component, params in parameters_dict.items(): + for param, value in params.items(): + env_file.write(f"{param}={value}\n") + + docker_compose_files = [ + mapping[component]["file"] for component in selected_components + ] + if docker_compose_files: + click.echo("Running Docker Compose...") + for file in docker_compose_files: + subprocess.run( + ["docker-compose", "-f", file, "up", "--build", "-d"], check=True + ) + + # Run docker-compose logs -f for each file + for file in docker_compose_files: + subprocess.run(["docker-compose", "-f", file, "logs", "-f"], check=True) + + click.echo("Application setup complete!") + subprocess.run(["docker", "ps"], check=True) + else: + click.echo("No components selected. Exiting.") + + +if __name__ == "__main__": + run_application() From 398e0594264e29cccbfb9e16a15d2569a6964ed8 Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Wed, 10 Apr 2024 09:52:37 +0530 Subject: [PATCH 02/16] added docker compose files for components --- cron-docker-compose.yml | 19 +++++++++ default-docker-compose.yml | 81 +++++++++++++++++++++++++++++++++++++ elk-docker-compose.yml | 43 ++++++++++++++++++++ nginx-docker-compose.yml | 44 ++++++++++++++++++++ postgres-docker-compose.yml | 27 +++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 cron-docker-compose.yml create mode 100644 default-docker-compose.yml create mode 100644 elk-docker-compose.yml create mode 100644 nginx-docker-compose.yml create mode 100644 postgres-docker-compose.yml diff --git a/cron-docker-compose.yml b/cron-docker-compose.yml new file mode 100644 index 000000000..a06202124 --- /dev/null +++ b/cron-docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.3' + +services: + cron: + build: ./cron + image: evgeniy-khyst/cron + environment: + COMPOSE_PROJECT_NAME: "${COMPOSE_PROJECT_NAME}" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./:/workdir:ro + restart: unless-stopped + networks: + - shoonya_backend + + +networks: + shoonya_backend: + external: true \ No newline at end of file diff --git a/default-docker-compose.yml b/default-docker-compose.yml new file mode 100644 index 000000000..fe57908a2 --- /dev/null +++ b/default-docker-compose.yml @@ -0,0 +1,81 @@ +version: '3.3' + +services: + web: + build: ./backend + command: python manage.py runserver 0.0.0.0:8000 + volumes: + - ./backend/:/usr/src/backend/ + - static_volume:/usr/src/backend/static + ports: + - 8000:8000 + depends_on: + - redis + networks: + - shoonya_backend + redis: + container_name: redis + image: "redis" + ports: + - 6379:6379 + networks: + - shoonya_backend + + celery: + container_name: celery-default + restart: always + build: ./backend + command: celery -A shoonya_backend.celery worker -Q default --concurrency=2 --loglevel=info + volumes: + - ./backend/:/usr/src/backend/ + depends_on: + - redis + - web + networks: + - shoonya_backend + + # This is the additional queue which contains the low-priority celery tasks. We can adjust the concurrency and workers alloted to this container. + celery2: + container_name: celery-low-priority + restart: always + build: ./backend + command: celery -A shoonya_backend.celery worker -Q functions --concurrency=2 --loglevel=info + volumes: + - ./backend/:/usr/src/backend/ + depends_on: + - redis + - web + networks: + - shoonya_backend + + # Celery beats - for scheduling daily e-mails + celery-beat: + build: ./backend + command: celery -A shoonya_backend.celery beat --loglevel=info + volumes: + - ./backend/:/usr/src/backend + depends_on: + - redis + - web + networks: + - shoonya_backend + + celery3: + container_name: celery-reports + restart: always + build: ./backend + command: celery -A shoonya_backend.celery worker -Q reports --concurrency=2 --loglevel=info + volumes: + - ./backend/:/usr/src/backend/ + depends_on: + - redis + - web + networks: + - shoonya_backend + +volumes: + static_volume: + +networks: + shoonya_backend: + external: true \ No newline at end of file diff --git a/elk-docker-compose.yml b/elk-docker-compose.yml new file mode 100644 index 000000000..f04d31199 --- /dev/null +++ b/elk-docker-compose.yml @@ -0,0 +1,43 @@ +version: '3.3' + +services: + elasticsearch: + container_name: elasticsearch + image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0 + volumes: + - ./elasticsearch/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml + # - elasticsearch_vol:/elasticsearch_data + environment: + - discovery.type=single-node + ports: + - "9200:9200" + - "9300:9300" + + kibana: + container_name: kibana + image: docker.elastic.co/kibana/kibana:7.14.0 + ports: + - 5601:5601 + depends_on: + - elasticsearch + + logstash: + container_name: logstash + image: docker.elastic.co/logstash/logstash:7.14.0 + hostname: shoonya_dev_logger + volumes: + - ./logstash_dev.conf:/usr/share/logstash/pipeline/logstash.conf + # - logs_vol:/logs + extra_hosts: + - "elasticsearch:elasticsearch" + command: logstash -f /usr/share/logstash/pipeline/logstash.conf + +volumes: + elasticsearch_vol: + external: true + logs_vol: + external: true + +networks: + shoonya_backend: + external: true \ No newline at end of file diff --git a/nginx-docker-compose.yml b/nginx-docker-compose.yml new file mode 100644 index 000000000..cb4be6d4b --- /dev/null +++ b/nginx-docker-compose.yml @@ -0,0 +1,44 @@ +version: '3.3' + +services: + nginx: + build: ./nginx + image: evgeniy-khyst/nginx + env_file: + - ./config.env + volumes: + - nginx_conf:/etc/nginx/sites + - letsencrypt_certs:/etc/letsencrypt + - certbot_acme_challenge:/var/www/certbot + - ./vhosts:/etc/nginx/vhosts + - static_volume:/backend/static + ports: + - "80:80" + - "443:443" + restart: unless-stopped + networks: + - shoonya_backend + + certbot: + build: ./certbot + image: evgeniy-khyst/certbot + env_file: + - ./config.env + volumes: + - letsencrypt_certs:/etc/letsencrypt + - certbot_acme_challenge:/var/www/certbot + networks: + - shoonya_backend + +volumes: + nginx_conf: + external: true + letsencrypt_certs: + external: true + certbot_acme_challenge: + static_volume: + + +networks: + shoonya_backend: + external: true \ No newline at end of file diff --git a/postgres-docker-compose.yml b/postgres-docker-compose.yml new file mode 100644 index 000000000..dde19b29b --- /dev/null +++ b/postgres-docker-compose.yml @@ -0,0 +1,27 @@ +version: '3.9' + +services: + + db: + image: postgres + restart: always + # set shared memory limit when using docker-compose + shm_size: 128mb + # or set shared memory limit when deploy via swarm stack + volumes: + - type: tmpfs + target: /dev/shm + tmpfs: + size: 134217728 # 128*2^20 bytes = 128Mb + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + networks: + - shoonya_backend + +networks: + shoonya_backend: + external: true \ No newline at end of file From cc32f6eacaee9ab1435b56acfff510bd94409ada Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Sun, 14 Apr 2024 12:17:34 +0530 Subject: [PATCH 03/16] updated the script for backend deployment --- deploy.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/deploy.py b/deploy.py index 09408e511..0426fb730 100644 --- a/deploy.py +++ b/deploy.py @@ -2,49 +2,103 @@ import json import subprocess +# mapping = { +# "Default Setup": { +# "file": "default-docker-compose.yml", +# "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", +# "parameters": { +# "DB_NAME": { +# "help": "If you set up a PG installation, enter the same credentials, leave these to default", +# "default": "postgres", +# }, +# "DB_USER": { +# "help": "If you set up a PG installation, enter the same credentials, leave these to default", +# "default": "postgres", +# }, +# "DB_PASSWORD": { +# "help": "If you set up a PG installation, enter the same credentials, leave these to default", +# "default": "postgres", +# }, +# "DB_HOST": { +# "help": "If you set up a PG installation, enter the same credentials, leave these to default", +# "default": "db", +# }, +# "SECRET_KEY": { +# "help": "Django secret key", +# "default": "abcd1234", +# }, +# "AZURE_CONNECTION_STRING": { +# "help": "AZURE storage string", +# "default": "AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net", +# }, +# "LOGS_CONTAINER_NAME": { +# "help": "Logs container name", +# "default": "logs", +# }, +# }, +# }, +# } + mapping = { "Default Setup": { - "file": "default.yml", + "file": "default-docker-compose.yml", "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", "parameters": { "DB_NAME": { "help": "If you set up a PG installation, enter the same credentials, leave these to default", "default": "postgres", + "warning": "Please provide a valid database name", }, "DB_USER": { "help": "If you set up a PG installation, enter the same credentials, leave these to default", "default": "postgres", + "warning": "Please provide a valid database user", }, "DB_PASSWORD": { "help": "If you set up a PG installation, enter the same credentials, leave these to default", "default": "postgres", + "warning": "Please provide a valid database password", }, "DB_HOST": { "help": "If you set up a PG installation, enter the same credentials, leave these to default", "default": "db", + "warning": "Please provide a valid database host", }, "SECRET_KEY": { "help": "Django secret key", "default": "abcd1234", + "warning": "Please provide a valid secret key", }, "AZURE_CONNECTION_STRING": { "help": "AZURE storage string", - "default": "", + "default": "AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net", + "warning": "Please provide a valid Azure connection string", }, "LOGS_CONTAINER_NAME": { "help": "Logs container name", "default": "logs", + "warning": "Please provide a valid logs container name", }, }, }, } +def handle_error(error_message): + click.secho(error_message, fg="red", bold=True) + exit(1) @click.command() def run_application(): - click.echo("Welcome to the application setup CLI!") + click.secho("Welcome to the application setup CLI!", fg="green", bold=True) # click.echo("Default configuration includes Django, Celery, and Redis.") - subprocess.run(["docker", "network", "create", "shoonya_backend"], check=True) + + try: + subprocess.run(["docker", "network", "create", "shoonya_backend"], check=True) + click.secho("Network created with the name shoonya_backend", fg="green", bold=True) + except subprocess.CalledProcessError: + click.secho("Network already exists with the name shoonya. Skipping creation.", fg="yellow") + + selected_components = [] parameters_dict = {} @@ -54,7 +108,7 @@ def run_application(): ) if install_postgres.upper() == "Y": subprocess.run( - ["docker-compose", "-f", "postgres.yml", "up", "--build", "-d"], check=True + ["docker-compose", "-f", "postgres-docker-compose.yml", "up", "--build", "-d"], check=True ) production = click.prompt( @@ -78,6 +132,7 @@ def run_application(): for param, details in parameters.items(): help_message = details.get("help", "") default_value = details.get("default", "") + warning = details.get("warning", "") value = click.prompt( f"Enter value for {param} ({help_message})", default=default_value, @@ -87,6 +142,8 @@ def run_application(): component_params[param] = value parameters_dict[key] = component_params + + if parameters_dict: with open("backend/.env", "w") as env_file: for component, params in parameters_dict.items(): @@ -107,10 +164,10 @@ def run_application(): for file in docker_compose_files: subprocess.run(["docker-compose", "-f", file, "logs", "-f"], check=True) - click.echo("Application setup complete!") + click.secho("Application setup complete!", fg="green", bold=True) subprocess.run(["docker", "ps"], check=True) else: - click.echo("No components selected. Exiting.") + click.secho("No components selected. Exiting.",fg="red",bold=True) if __name__ == "__main__": From 8021afcbdea384773ba38d6463c73e866ccee449 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Tue, 16 Apr 2024 08:55:33 +0530 Subject: [PATCH 04/16] updated the script for any error handling --- deploy.py | 196 +++++++++++++++++++++++++----------------------------- 1 file changed, 90 insertions(+), 106 deletions(-) diff --git a/deploy.py b/deploy.py index 0426fb730..4b4a4a917 100644 --- a/deploy.py +++ b/deploy.py @@ -1,44 +1,7 @@ import click import json import subprocess - -# mapping = { -# "Default Setup": { -# "file": "default-docker-compose.yml", -# "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", -# "parameters": { -# "DB_NAME": { -# "help": "If you set up a PG installation, enter the same credentials, leave these to default", -# "default": "postgres", -# }, -# "DB_USER": { -# "help": "If you set up a PG installation, enter the same credentials, leave these to default", -# "default": "postgres", -# }, -# "DB_PASSWORD": { -# "help": "If you set up a PG installation, enter the same credentials, leave these to default", -# "default": "postgres", -# }, -# "DB_HOST": { -# "help": "If you set up a PG installation, enter the same credentials, leave these to default", -# "default": "db", -# }, -# "SECRET_KEY": { -# "help": "Django secret key", -# "default": "abcd1234", -# }, -# "AZURE_CONNECTION_STRING": { -# "help": "AZURE storage string", -# "default": "AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net", -# }, -# "LOGS_CONTAINER_NAME": { -# "help": "Logs container name", -# "default": "logs", -# }, -# }, -# }, -# } - + mapping = { "Default Setup": { "file": "default-docker-compose.yml", @@ -83,92 +46,113 @@ }, } -def handle_error(error_message): +def echo_error(error_message): click.secho(error_message, fg="red", bold=True) exit(1) +def echo_success(success_message): + click.secho(success_message, fg="green", bold=True) + +def echo_warning(warning_message): + click.secho(warning_message, fg="yellow", bold=True) + @click.command() def run_application(): - click.secho("Welcome to the application setup CLI!", fg="green", bold=True) - # click.echo("Default configuration includes Django, Celery, and Redis.") + echo_success("Welcome to the application setup CLI!") try: subprocess.run(["docker", "network", "create", "shoonya_backend"], check=True) - click.secho("Network created with the name shoonya_backend", fg="green", bold=True) + echo_success("Network created with the name shoonya_backend") except subprocess.CalledProcessError: - click.secho("Network already exists with the name shoonya. Skipping creation.", fg="yellow") + echo_warning("Network already exists with the name shoonya. Skipping creation.") selected_components = [] parameters_dict = {} - # Ask user if they want PostgreSQL installation - install_postgres = click.prompt( - "Do you want to include PostgreSQL installation? (Y/N)", default="N" - ) - if install_postgres.upper() == "Y": - subprocess.run( - ["docker-compose", "-f", "postgres-docker-compose.yml", "up", "--build", "-d"], check=True - ) - - production = click.prompt( - "Do you want to run the application in production mode? (Y/N)", default="N" - ) - if production.upper() == "N": - click.echo("Running in production mode") - parameters_dict["ENVIRONMENT"] = dict({ - "ENV" : "dev" - }) - for key, value in mapping.items(): - choice = click.prompt( - f"Do you want to include {key}? ({value['description']}) (Y/N)", default="N" + try : + # Ask user if they want PostgreSQL installation + install_postgres = click.prompt( + "Do you want to include PostgreSQL installation? (Y/N)", default="N" ) - if choice.upper() == "Y": - selected_components.append(key) - parameters = value.get("parameters") - if parameters: - click.echo(f"Please provide values for parameters for {key}:") - component_params = {} - for param, details in parameters.items(): - help_message = details.get("help", "") - default_value = details.get("default", "") - warning = details.get("warning", "") - value = click.prompt( - f"Enter value for {param} ({help_message})", - default=default_value, - ) - if not value: - value = default_value - component_params[param] = value - parameters_dict[key] = component_params - - - - if parameters_dict: - with open("backend/.env", "w") as env_file: - for component, params in parameters_dict.items(): - for param, value in params.items(): - env_file.write(f"{param}={value}\n") - - docker_compose_files = [ - mapping[component]["file"] for component in selected_components - ] - if docker_compose_files: - click.echo("Running Docker Compose...") - for file in docker_compose_files: + if install_postgres.upper() == "Y": subprocess.run( - ["docker-compose", "-f", file, "up", "--build", "-d"], check=True + ["docker-compose", "-f", "postgres-docker-compose.yml", "up", "--build", "-d"], check=True ) - # Run docker-compose logs -f for each file - for file in docker_compose_files: - subprocess.run(["docker-compose", "-f", file, "logs", "-f"], check=True) - - click.secho("Application setup complete!", fg="green", bold=True) - subprocess.run(["docker", "ps"], check=True) - else: - click.secho("No components selected. Exiting.",fg="red",bold=True) - + production = click.prompt( + "Do you want to run the application in production mode? (Y/N)", default="N" + ) + if production.upper() == "N": + click.echo("Running in production mode") + parameters_dict["ENVIRONMENT"] = dict({ + "ENV" : "dev" + }) + for key, value in mapping.items(): + choice = click.prompt( + f"Do you want to include {key}? ({value['description']}) (Y/N)", default="N" + ) + if choice.upper() == "Y": + selected_components.append(key) + parameters = value.get("parameters") + if parameters: + click.echo(f"Please provide values for parameters for {key}:") + component_params = {} + for param, details in parameters.items(): + help_message = details.get("help", "") + default_value = details.get("default", "") + warning = details.get("warning", "") + value = click.prompt( + f"Enter value for {param} ({help_message})", + default=default_value, + ) + if not value: + value = default_value + component_params[param] = value + parameters_dict[key] = component_params + if parameters_dict: + with open("backend/.env", "w") as env_file: + for component, params in parameters_dict.items(): + for param, value in params.items(): + env_file.write(f"{param}={value}\n") + + docker_compose_files = [ + mapping[component]["file"] for component in selected_components + ] + if docker_compose_files: + click.echo("Running Docker Compose...") + for file in docker_compose_files: + subprocess.run( + ["docker-compose", "-f", file, "up", "--build", "-d"], check=True + ) + + # Run docker-compose logs -f for each file + for file in docker_compose_files: + subprocess.run(["docker-compose", "-f", file, "logs", "-f"], check=True) + + echo_success("Application setup complete!") + subprocess.run(["docker", "ps"], check=True) + else: + echo_error("No components selected. Exiting.") + + + except Exception as e: + print(f"An error occurred: {e}") + echo_error("An error occurred. Exiting. ") + echo_error("Stopping all running containers...") + if docker_compose_files: + for file in docker_compose_files: + subprocess.run( + ["docker-compose", "-f", file, "down"], check=True + ) + + # Run docker-compose logs -f for each file + for file in docker_compose_files: + subprocess.run(["docker-compose", "-f", file, "logs", "-f"], check=True) + + else: + echo_error("No components selected. Exiting.") + exit(1) if __name__ == "__main__": run_application() From 0949b37c600bcffea96872351425adc95b8a84c0 Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Tue, 16 Apr 2024 09:59:07 +0530 Subject: [PATCH 05/16] added dockerized deployment instructions to readme --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e093b8b17..5281a3da1 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,61 @@ GOOGLE_APPLICATION_CREDENTIALS="/path/to/gcloud-key.json" ### Docker Installation -`cd` back to the root folder .Once inside, build the docker containers: + + + +```markdown +# Application Setup Guide + +This guide will walk you through setting up the application using different methods and components. + +## Running the Script + +To run the setup script, follow these steps: + +1. Clone this repository to your local machine. +2. Navigate to the repository directory. +3. Run the following command: + + ```bash + python setup.py + ``` + +4. Follow the prompts to select components and provide required parameters. + +## Components + +### Default Setup + +- **Description:** Required for the application to work. Contains a docker deployment of Django, Celery, and Redis. + + **Parameters:** + - `DB_NAME` + - **Description:** Database name. + - **Default:** `postgres` + - `DB_USER` + - **Description:** Database user. + - **Default:** `postgres` + - `DB_PASSWORD` + - **Description:** Database password. + - **Default:** `postgres` + - `DB_HOST` + - **Description:** Database host. + - **Default:** `db` + - `SECRET_KEY` + - **Description:** Django secret key. + - **Default:** `abcd1234` + - `AZURE_CONNECTION_STRING` + - **Description:** Azure storage string. + - **Default:** `AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net` + - `LOGS_CONTAINER_NAME` + - **Description:** Logs container name. + - **Default:** `logs` + + + + + + + If there were no errors, congratulations! The project is up and running. From 0719265491aa2588d4236f53417ad922fce7c011 Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Tue, 16 Apr 2024 14:54:43 +0530 Subject: [PATCH 06/16] added components to mapping --- NewReadme.md | 0 deploy.py | 123 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 NewReadme.md diff --git a/NewReadme.md b/NewReadme.md new file mode 100644 index 000000000..e69de29bb diff --git a/deploy.py b/deploy.py index 4b4a4a917..62cc0eef8 100644 --- a/deploy.py +++ b/deploy.py @@ -2,28 +2,28 @@ import json import subprocess -mapping = { +component_mapping = { "Default Setup": { "file": "default-docker-compose.yml", "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", "parameters": { "DB_NAME": { - "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "help": "If you set up a PG installation, leave these to default", "default": "postgres", "warning": "Please provide a valid database name", }, "DB_USER": { - "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "help": "If you set up a PG installation, leave these to default", "default": "postgres", "warning": "Please provide a valid database user", }, "DB_PASSWORD": { - "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "help": "If you set up a PG installation, leave these to default", "default": "postgres", "warning": "Please provide a valid database password", }, "DB_HOST": { - "help": "If you set up a PG installation, enter the same credentials, leave these to default", + "help": "If you set up a PG installation, leave these to default", "default": "db", "warning": "Please provide a valid database host", }, @@ -44,6 +44,91 @@ }, }, }, + "Elasticsearch-Logstash-Kibana": { + "file": "elk-docker-compose.yml", + "description": "ELK stack for logging monitoring etc", + "parameters": { + "ELASTICSEARCH_URL": { + "help": "url for elasticsearch", + "default": "elasticsearch:9200", + "warning": "Please provide a valid elasticsearch endpoint", + }, + "INDEX_NAME": { + "help": "", + "default": "", + "warning": "", + } + }, + }, + "Email Service": { + "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", + "parameters": { + "EMAIL_HOST": { + "help": "If you set up a PG installation, leave these to default", + "default": "postgres", + "warning": "Please provide a valid database name", + }, + "SMTP_USERNAME": { + "help": "If you set up a PG installation, leave these to default", + "default": "postgres", + "warning": "Please provide a valid database user", + }, + "SMTP_PASSWORD": { + "help": "If you set up a PG installation, leave these to default", + "default": "postgres", + "warning": "Please provide a valid database password", + }, + "DEFAULT_FROM_EMAIL": { + "help": "If you set up a PG installation, leave these to default", + "default": "db", + "warning": "Please provide a valid database host", + } + }, + }, + "Logging": { + + "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", + "parameters": { + "LOGGING": { + "help": "If you set up a PG installation, leave these to default", + "default": "postgres", + "warning": "Please provide a valid database name", + }, + "LOG_LEVEL": { + "help": "If you set up a PG installation, leave these to default", + "default": "postgres", + "warning": "Please provide a valid database user", + } + }, + }, + "MINIO": { + "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", + "parameters": { + "MINIO_ACCESS_KEY": { + "help": "", + "default": "", + "warning": "", + }, + "MINIO_SECRET_KEY": { + "help": "", + "default": "", + "warning": "", + }, + "MINIO_ENDPOINT": { + "help": "", + "default": "", + "warning": "", + }, + }, + } +} + +environment = { + "ENVIRONMENT": { + "default" : "dev", + "help": "The environment in which the application is running. PROD : Production, DEV : Development" + }, + } def echo_error(error_message): @@ -68,9 +153,18 @@ def run_application(): selected_components = [] + docker_compose_files = [] parameters_dict = {} try : + production = click.prompt( + "Do you want to run the application in production mode? (Y/N)", default="N" + ) + if production.upper() == "N": + click.echo("Running in development mode") + parameters_dict["ENVIRONMENT"] = dict({ + "ENV" : "dev" + }) # Ask user if they want PostgreSQL installation install_postgres = click.prompt( "Do you want to include PostgreSQL installation? (Y/N)", default="N" @@ -80,20 +174,17 @@ def run_application(): ["docker-compose", "-f", "postgres-docker-compose.yml", "up", "--build", "-d"], check=True ) - production = click.prompt( - "Do you want to run the application in production mode? (Y/N)", default="N" - ) - if production.upper() == "N": - click.echo("Running in production mode") - parameters_dict["ENVIRONMENT"] = dict({ - "ENV" : "dev" - }) + for key, value in mapping.items(): choice = click.prompt( f"Do you want to include {key}? ({value['description']}) (Y/N)", default="N" ) if choice.upper() == "Y": selected_components.append(key) + #modify the next line such that it only appends if there is a key called "file" in the value + if "file" in value: + docker_compose_files.append(value["file"]) + parameters = value.get("parameters") if parameters: click.echo(f"Please provide values for parameters for {key}:") @@ -106,6 +197,7 @@ def run_application(): f"Enter value for {param} ({help_message})", default=default_value, ) + if not value: value = default_value component_params[param] = value @@ -116,9 +208,7 @@ def run_application(): for param, value in params.items(): env_file.write(f"{param}={value}\n") - docker_compose_files = [ - mapping[component]["file"] for component in selected_components - ] + if docker_compose_files: click.echo("Running Docker Compose...") for file in docker_compose_files: @@ -156,3 +246,4 @@ def run_application(): if __name__ == "__main__": run_application() + \ No newline at end of file From acf7fb45f78f68c955c61488a29061b25148770d Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Tue, 16 Apr 2024 22:45:04 +0530 Subject: [PATCH 07/16] added parameters for most components in the mapping --- deploy.py | 196 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 170 insertions(+), 26 deletions(-) diff --git a/deploy.py b/deploy.py index 62cc0eef8..5289863f8 100644 --- a/deploy.py +++ b/deploy.py @@ -1,7 +1,7 @@ import click import json import subprocess - + component_mapping = { "Default Setup": { "file": "default-docker-compose.yml", @@ -27,6 +27,11 @@ "default": "db", "warning": "Please provide a valid database host", }, + "CELERY_BROKER_URL": { + "help": "Broker for celery tasks", + "default": "redis://redis:6379/0", + "warning": "", + }, "SECRET_KEY": { "help": "Django secret key", "default": "abcd1234", @@ -44,6 +49,26 @@ }, }, }, + "Nginx_Certbot": { + "description": "Nginx and Certbot setup for serving HTTPS traffic", + "parameters": { + "DOMAINS": { + "help": "Domains for which the certificates will be obtained", + "default": "", + "warning": "Please provide valid domains", + }, + "CERTBOT_TEST_CERT": { + "help": "Whether to use Certbot's test certificate", + "default": "0", + "warning": "", + }, + "CERTBOT_RSA_KEY_SIZE": { + "help": "Size of RSA key for the certificate", + "default": "4096", + "warning": "", + }, + }, + }, "Elasticsearch-Logstash-Kibana": { "file": "elk-docker-compose.yml", "description": "ELK stack for logging monitoring etc", @@ -57,7 +82,123 @@ "help": "", "default": "", "warning": "", - } + }, + }, + }, + "Flower": { + "file": "flower-docker-compose.yml", + "description": "Flower for monitoring celery tasks", + "parameters": { + "FLOWER_ADDRESS": { + "help": "Address for accessing Flower", + "default": "flower", + "warning": "Please provide a valid Flower address", + }, + "FLOWER_PORT": { + "help": "Port for accessing Flower", + "default": "5555", + "warning": "Please provide a valid port number for Flower", + }, + "FLOWER_USERNAME": { + "help": "Username for Flower", + "default": "shoonya", + "warning": "Please provide a valid username for Flower", + }, + "FLOWER_PASSWORD": { + "help": "Password for Flower", + "default": "flower123", + "warning": "Please provide a valid password for Flower", + }, + }, + }, + "Google_Application_Credentials": { + "description": "Google Application Credentials for accessing Google APIs", + "parameters": { + "type": { + "help": "Type of service account", + "default": "", + "warning": "Please provide a valid type", + }, + "project_id": { + "help": "Project ID", + "default": "", + "warning": "Please provide a valid project ID", + }, + "private_key_id": { + "help": "Private key ID", + "default": "", + "warning": "Please provide a valid private key ID", + }, + "private_key": { + "help": "Private key", + "default": "", + "warning": "Please provide a valid private key", + }, + "client_email": { + "help": "Client email", + "default": "", + "warning": "Please provide a valid client email", + }, + "client_id": { + "help": "Client ID", + "default": "", + "warning": "Please provide a valid client ID", + }, + "auth_uri": { + "help": "Authorization URI", + "default": "", + "warning": "Please provide a valid authorization URI", + }, + "token_uri": { + "help": "Token URI", + "default": "", + "warning": "Please provide a valid token URI", + }, + "auth_provider_x509_cert_url": { + "help": "Auth provider X.509 certificate URL", + "default": "", + "warning": "Please provide a valid Auth provider X.509 certificate URL", + }, + "client_x509_cert_url": { + "help": "Client X.509 certificate URL", + "default": "", + "warning": "Please provide a valid Client X.509 certificate URL", + }, + "universe_domain": { + "help": "Universe domain", + "default": "", + "warning": "Please provide a valid universe domain", + }, + }, + }, + "Ask_Dhruva": { + "description": "Component for interacting with Dhruva ASR service", + "parameters": { + "ASR_DHRUVA_URL": { + "help": "URL for Dhruva ASR service", + "default": "", + "warning": "Please provide a valid Dhruva ASR service URL", + }, + "ASR_DHRUVA_AUTHORIZATION": { + "help": "Authorization token for Dhruva ASR service", + "default": "", + "warning": "Please provide a valid authorization token for Dhruva ASR service", + }, + }, + }, + "Indic_Trans_V2": { + "description": "Component for interacting with Indic Trans V2 service", + "parameters": { + "INDIC_TRANS_V2_KEY": { + "help": "API key for Indic Trans V2 service", + "default": "", + "warning": "Please provide a valid API key for Indic Trans V2 service", + }, + "INDIC_TRANS_V2_URL": { + "help": "URL for Indic Trans V2 service", + "default": "", + "warning": "Please provide a valid URL for Indic Trans V2 service", + }, }, }, "Email Service": { @@ -82,11 +223,10 @@ "help": "If you set up a PG installation, leave these to default", "default": "db", "warning": "Please provide a valid database host", - } + }, }, }, "Logging": { - "description": "Required for the application to work. Contains a docker deployment of Django, Celery, and Redis", "parameters": { "LOGGING": { @@ -98,7 +238,7 @@ "help": "If you set up a PG installation, leave these to default", "default": "postgres", "warning": "Please provide a valid database user", - } + }, }, }, "MINIO": { @@ -120,71 +260,79 @@ "warning": "", }, }, - } + }, } environment = { "ENVIRONMENT": { - "default" : "dev", - "help": "The environment in which the application is running. PROD : Production, DEV : Development" + "default": "dev", + "help": "The environment in which the application is running. PROD : Production, DEV : Development", }, - } + def echo_error(error_message): click.secho(error_message, fg="red", bold=True) exit(1) + def echo_success(success_message): click.secho(success_message, fg="green", bold=True) + def echo_warning(warning_message): click.secho(warning_message, fg="yellow", bold=True) + @click.command() def run_application(): echo_success("Welcome to the application setup CLI!") - try: + try: subprocess.run(["docker", "network", "create", "shoonya_backend"], check=True) echo_success("Network created with the name shoonya_backend") except subprocess.CalledProcessError: echo_warning("Network already exists with the name shoonya. Skipping creation.") - selected_components = [] docker_compose_files = [] parameters_dict = {} - try : + try: production = click.prompt( "Do you want to run the application in production mode? (Y/N)", default="N" ) if production.upper() == "N": click.echo("Running in development mode") - parameters_dict["ENVIRONMENT"] = dict({ - "ENV" : "dev" - }) + parameters_dict["ENVIRONMENT"] = dict({"ENV": "dev"}) # Ask user if they want PostgreSQL installation install_postgres = click.prompt( "Do you want to include PostgreSQL installation? (Y/N)", default="N" ) if install_postgres.upper() == "Y": subprocess.run( - ["docker-compose", "-f", "postgres-docker-compose.yml", "up", "--build", "-d"], check=True + [ + "docker-compose", + "-f", + "postgres-docker-compose.yml", + "up", + "--build", + "-d", + ], + check=True, ) - for key, value in mapping.items(): choice = click.prompt( - f"Do you want to include {key}? ({value['description']}) (Y/N)", default="N" + f"Do you want to include {key}? ({value['description']}) (Y/N)", + default="N", ) if choice.upper() == "Y": selected_components.append(key) - #modify the next line such that it only appends if there is a key called "file" in the value + # modify the next line such that it only appends if there is a key called "file" in the value if "file" in value: docker_compose_files.append(value["file"]) - + parameters = value.get("parameters") if parameters: click.echo(f"Please provide values for parameters for {key}:") @@ -208,7 +356,6 @@ def run_application(): for param, value in params.items(): env_file.write(f"{param}={value}\n") - if docker_compose_files: click.echo("Running Docker Compose...") for file in docker_compose_files: @@ -225,16 +372,13 @@ def run_application(): else: echo_error("No components selected. Exiting.") - except Exception as e: print(f"An error occurred: {e}") echo_error("An error occurred. Exiting. ") echo_error("Stopping all running containers...") if docker_compose_files: for file in docker_compose_files: - subprocess.run( - ["docker-compose", "-f", file, "down"], check=True - ) + subprocess.run(["docker-compose", "-f", file, "down"], check=True) # Run docker-compose logs -f for each file for file in docker_compose_files: @@ -244,6 +388,6 @@ def run_application(): echo_error("No components selected. Exiting.") exit(1) + if __name__ == "__main__": run_application() - \ No newline at end of file From 8c1d02ff8348840fe3978bfcf6f42e57d6c9b5c5 Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Tue, 16 Apr 2024 23:37:53 +0530 Subject: [PATCH 08/16] added new readme draft --- NewReadme.md | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ deploy.py | 30 +++++++---- 2 files changed, 160 insertions(+), 10 deletions(-) diff --git a/NewReadme.md b/NewReadme.md index e69de29bb..d1c0c5ce7 100644 --- a/NewReadme.md +++ b/NewReadme.md @@ -0,0 +1,140 @@ +# Shoonya Backend + +Repository for Shoonya's backend. + +## Pre-requisites + +The project was created using [Python 3.7](https://www.python.org/downloads/). All major dependencies along with the versions are listed in the `backend/deploy/requirements.txt` file. + +## Installation + + + +# Project Name README + +## Prerequisites +- Docker Engine/Docker Desktop running + +## Database Setup +To set up a PostgreSQL container: +- When prompted during the script execution, enter 'Y' to include PostgreSQL installation. +- Alternatively, manually provide the following database variables in the `.env` file: + - `DB_NAME` + - `DB_USER` + - `DB_PASSWORD` + - `DB_HOST` + +## Default Components +This section includes the default setup required for the application to work. It contains Docker deployments of Django, Celery, and Redis. + +### Django +- Description: Django is a high-level Python web framework. +- Configuration: + - `SECRET_KEY`: Django secret key + +### Celery +- Description: Celery is an asynchronous task queue/job queue based on distributed message passing. +- Configuration: + - `CELERY_BROKER_URL`: Broker for Celery tasks + +### Redis +- Description: Redis is an open-source, in-memory data structure store used as a database, cache, and message broker. +- Configuration: None + +## Elasticsearch-Logstash-Kibana +This section contains the ELK stack for logging monitoring etc. + +- Elasticsearch + - Description: Elasticsearch is a distributed, RESTful search and analytics engine. + - Configuration: + - `ELASTICSEARCH_URL`: URL for Elasticsearch + - `INDEX_NAME`: Index name + +- Logstash + - Description: Logstash is a server-side data processing pipeline that ingests data from multiple sources simultaneously, transforms it, and then sends it to a "stash" like Elasticsearch. + - Configuration: None + +- Kibana + - Description: Kibana is an open-source data visualization dashboard for Elasticsearch. + - Configuration: None + +## Nginx-Certbot +This section contains Nginx and Certbot setup for serving HTTPS traffic. + +- Nginx + - Description: Nginx is a web server that can also be used as a reverse proxy, load balancer, mail proxy, and HTTP cache. + - Configuration: None + +- Certbot + - Description: Certbot is a free, open-source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS. + - Configuration: None + +## Additional Services + +### Google Application Credentials +- Description: Google Application Credentials for accessing Google APIs. +- Parameters: + - `type` + - `project_id` + - `private_key_id` + - `private_key` + - `client_email` + - `client_id` + - `auth_uri` + - `token_uri` + - `auth_provider_x509_cert_url` + - `client_x509_cert_url` + - `universe_domain` + +### Ask_Dhruva +- Description: Component for interacting with Dhruva ASR service. +- Parameters: + - `ASR_DHRUVA_URL`: URL for Dhruva ASR service + - `ASR_DHRUVA_AUTHORIZATION`: Authorization token for Dhruva ASR service + +### Indic_Trans_V2 +- Description: Component for interacting with Indic Trans V2 service. +- Parameters: + - `INDIC_TRANS_V2_KEY`: API key for Indic Trans V2 service + - `INDIC_TRANS_V2_URL`: URL for Indic Trans V2 service + +### Email Service +- Description: Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. +- Parameters: + - `EMAIL_HOST` + - `SMTP_USERNAME` + - `SMTP_PASSWORD` + - `DEFAULT_FROM_EMAIL` + +### Logging +- Description: Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. +- Parameters: + - `LOGGING` + - `LOG_LEVEL` + +### MINIO +- Description: Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. +- Parameters: + - `MINIO_ACCESS_KEY` + - `MINIO_SECRET_KEY` + - `MINIO_ENDPOINT` + +## Running the Setup Script +To run the setup script: +1. Clone this repository to your local machine. +2. Navigate to the root directory of the project. +3. Run the following command: `python deploy.py` + + +## What the script does? +- Automatically creates a Docker network named `shoonya_backend`. +- Prompts the user to choose whether to run the application in production mode. +- Guides the user through setting up a PostgreSQL container if desired. +- Allows selection of components and sets up Docker Compose files accordingly. +- Manages environment variables in the `.env` file for each selected component. +- Deploys Docker containers for selected components. +- Provides feedback and error handling throughout the setup process. + + + + diff --git a/deploy.py b/deploy.py index 5289863f8..c87fa70bc 100644 --- a/deploy.py +++ b/deploy.py @@ -27,6 +27,16 @@ "default": "db", "warning": "Please provide a valid database host", }, + "REDIS_HOST": { + "help": "If you are using the inbuilt redis as broker, leave these to default", + "default": "redis", + "warning": "Please provide a valid redis host", + }, + "REDIS_PORT": { + "help": "If you are using the inbuilt redis as broker, leave these to default", + "default": "6379", + "warning": "Please provide a valid redis port", + }, "CELERY_BROKER_URL": { "help": "Broker for celery tasks", "default": "redis://redis:6379/0", @@ -37,16 +47,6 @@ "default": "abcd1234", "warning": "Please provide a valid secret key", }, - "AZURE_CONNECTION_STRING": { - "help": "AZURE storage string", - "default": "AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net", - "warning": "Please provide a valid Azure connection string", - }, - "LOGS_CONTAINER_NAME": { - "help": "Logs container name", - "default": "logs", - "warning": "Please provide a valid logs container name", - }, }, }, "Nginx_Certbot": { @@ -268,6 +268,16 @@ "default": "dev", "help": "The environment in which the application is running. PROD : Production, DEV : Development", }, + "AZURE_CONNECTION_STRING": { + "help": "AZURE storage string", + "default": "AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net", + + }, + "LOGS_CONTAINER_NAME": { + "help": "Logs container name", + "default": "logs", + + }, } From e6122394b40199eefbb0e116dcc60852e162146c Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Wed, 17 Apr 2024 23:34:23 +0530 Subject: [PATCH 09/16] updated the readme file for better code understanding --- NewReadme.md | 182 ++++++++++++++++++++++++++++++++++++++++++--------- deploy.py | 4 +- 2 files changed, 153 insertions(+), 33 deletions(-) diff --git a/NewReadme.md b/NewReadme.md index d1c0c5ce7..5a54325ec 100644 --- a/NewReadme.md +++ b/NewReadme.md @@ -10,12 +10,56 @@ The project was created using [Python 3.7](https://www.python.org/downloads/). A +
+ Wow, so fancy + WOW, SO BOLD +
+ # Project Name README ## Prerequisites -- Docker Engine/Docker Desktop running +- **Docker Engine/Docker Desktop running** + You may download Docker Desktop from the table given below + + | Systems| Link | + | ---------- | ------ | + | Windows | https://docs.docker.com/desktop/install/windows-install/ | + | Ubuntu | https://docs.docker.com/desktop/install/ubuntu/ | + | Unix/Mac | https://docs.docker.com/desktop/install/mac-install/| +- Python Version 3.7 or above +- An Azure account subscription. + +## Backend Setup +The whole backend setup is divided into mainly 5 Components +``` +Backend Setup +| +|-- 1. Database Setup +| +|-- 2. Default Components +| |-- a) Django +| |-- b) Celery +| |-- c) Redis +| +|-- 3. Elastic Logstash Kibana (ELK) & Flower Confirguration +| +|-- 4. Nginx-Certbot +| +|-- 5. Additional Services +| |-- a) Google Application Credentials +| |-- b) Ask_Dhruva +| |-- c) Indic_Trans_V2 +| |-- d) Email Service +| |-- e) Logging +| |-- f) Minio +``` + + +
+ + 1. Database Setup + -## Database Setup To set up a PostgreSQL container: - When prompted during the script execution, enter 'Y' to include PostgreSQL installation. - Alternatively, manually provide the following database variables in the `.env` file: @@ -23,25 +67,51 @@ To set up a PostgreSQL container: - `DB_USER` - `DB_PASSWORD` - `DB_HOST` +
+ -## Default Components -This section includes the default setup required for the application to work. It contains Docker deployments of Django, Celery, and Redis. +
+ + 2. Default Components + -### Django -- Description: Django is a high-level Python web framework. +This section outlines the essential setup needed for the application to function properly. It encompasses Docker deployments for Django, Celery, and Redis, which form the core components of our application infrastructure. + +#### a) Django +- **Description:** Django is a Python-based framework for web development, providing tools and features to build robust web applications quickly and efficiently. - Configuration: - - `SECRET_KEY`: Django secret key + - `SECRET_KEY`: Django secret key either enter manually or generate using the command + + To create a new secret key, run the following commands (within the virtual environment): + ``` + # Open a Python shell + python backend/manage.py shell -### Celery -- Description: Celery is an asynchronous task queue/job queue based on distributed message passing. + >> from django.core.management.utils import get_random_secret_key + >> get_random_secret_key() + ``` + +#### b) Celery +- **Description:** Celery is a system for asynchronous task processing based on distributed message passing, allowing computationally intensive operations to run in the background without impacting the main application's performance. - Configuration: - `CELERY_BROKER_URL`: Broker for Celery tasks + -### Redis +#### c) Redis - Description: Redis is an open-source, in-memory data structure store used as a database, cache, and message broker. -- Configuration: None +- Configuration: + - `REDIS_HOST`: Need to configue the port + - `REDIS_PORT` : Need to configure the port + +
+ +
+ + 3. ELK & Flower configuration + -## Elasticsearch-Logstash-Kibana + +#### a) Elasticsearch-Logstash-Kibanas This section contains the ELK stack for logging monitoring etc. - Elasticsearch @@ -58,7 +128,29 @@ This section contains the ELK stack for logging monitoring etc. - Description: Kibana is an open-source data visualization dashboard for Elasticsearch. - Configuration: None -## Nginx-Certbot + + +#### b) Flower Confirguation +- Flower is a web-based tool for monitoring and administrating Celery clusters. It allows you to keep track of tasks as they flow through your system, inspect the +system's health, and perform administrative operations +like shutting down workers. + +- Additionally, Flower would be monitoring the +tasks defined in our tasks/ directory in +the `backend/directory`. + +- Configuration: + - `FLOWER_USERNAME`: Flower username + - `FLOWER_PASSWORD`: Flower password + - `FLOWER_PORT`: Need to configure + +
+ +
+ + 4. Nginx-Certbot + + This section contains Nginx and Certbot setup for serving HTTPS traffic. - Nginx @@ -69,10 +161,19 @@ This section contains Nginx and Certbot setup for serving HTTPS traffic. - Description: Certbot is a free, open-source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS. - Configuration: None -## Additional Services +
+ + +
+ + 5 Additional Services + -### Google Application Credentials -- Description: Google Application Credentials for accessing Google APIs. + +These are the additional services that were not only present in certain confirguration but its actually present in whole files some of them are global variables and some are services. + +#### a) Google Application Credentials +- **Description**: Google Application Credentials are used to authenticate and authorize applications to use Google Cloud APIs. They are a key part of Google Cloud's IAM (Identity and Access Management) system, and they allow the application to interact with Google's services securely. - Parameters: - `type` - `project_id` @@ -86,44 +187,63 @@ This section contains Nginx and Certbot setup for serving HTTPS traffic. - `client_x509_cert_url` - `universe_domain` -### Ask_Dhruva -- Description: Component for interacting with Dhruva ASR service. +#### b) Ask_Dhruva +- Description: Component for interacting with Dhruva ASR service. This service is likely used in your application to convert spoken language into written text. - Parameters: - - `ASR_DHRUVA_URL`: URL for Dhruva ASR service - - `ASR_DHRUVA_AUTHORIZATION`: Authorization token for Dhruva ASR service + - `ASR_DHRUVA_URL`: Parameter is used to tell your application where to send requests for speech recognition. + - `ASR_DHRUVA_AUTHORIZATION`: Authorization token for Dhruva ASR service. -### Indic_Trans_V2 +#### c) Indic_Trans_V2 - Description: Component for interacting with Indic Trans V2 service. - Parameters: - `INDIC_TRANS_V2_KEY`: API key for Indic Trans V2 service - `INDIC_TRANS_V2_URL`: URL for Indic Trans V2 service -### Email Service -- Description: Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. +#### d) Email Service +- **Description**: The Email Service is likely used in your application to send emails. This could be for a variety of purposes such as sending notifications, password resets, confirmation emails, sending reports etc. - Parameters: - `EMAIL_HOST` - `SMTP_USERNAME` - `SMTP_PASSWORD` - `DEFAULT_FROM_EMAIL` -### Logging -- Description: Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. -- Parameters: - - `LOGGING` - - `LOG_LEVEL` +#### e) Logging +- Description: +Logging is used to record events or actions that occur during the execution of your program. It's a crucial part of software development for debugging and monitoring purposes -### MINIO -- Description: Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. +- Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. +- Parameters: + - `LOGGING` : This is a boolean value (either 'true' or +'false') that determines whether logging is enabled. + - `LOG_LEVEL` : This sets the level of logging. 'INFO' will +log all INFO, WARNING, ERROR, and CRITICAL level +logs. + +#### f) MINIO +- Description: MinIO is an open-source, high-performance, AWS S3 compatible object storage system. It is typically used in applications for storing unstructured data like photos, videos, log files, backups, and container/VM images. - Parameters: - `MINIO_ACCESS_KEY` - `MINIO_SECRET_KEY` - `MINIO_ENDPOINT` +
+ ## Running the Setup Script + To run the setup script: 1. Clone this repository to your local machine. + +```bash +git clone "https://github.com/AI4Bharat/Shoonya-Backend" +``` 2. Navigate to the root directory of the project. -3. Run the following command: `python deploy.py` +```bash +cd Shoonya_Backend +``` +3. Run the following command: `python deploy.py` make sure the docker engine is running on your system +4. Provide the details that has been asking in the prompt and it will automatically create & run the docker containers, volumnes and processes + + ## What the script does? diff --git a/deploy.py b/deploy.py index c87fa70bc..a1886eb1c 100644 --- a/deploy.py +++ b/deploy.py @@ -332,7 +332,7 @@ def run_application(): check=True, ) - for key, value in mapping.items(): + for key, value in component_mapping.items(): choice = click.prompt( f"Do you want to include {key}? ({value['description']}) (Y/N)", default="N", @@ -364,7 +364,7 @@ def run_application(): with open("backend/.env", "w") as env_file: for component, params in parameters_dict.items(): for param, value in params.items(): - env_file.write(f"{param}={value}\n") + env_file.write(f"{param}='{value}'\n") if docker_compose_files: click.echo("Running Docker Compose...") From 7732bf8be14ba11a22ff6f4893de5ddb2cf91acd Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Wed, 1 May 2024 11:47:56 +0530 Subject: [PATCH 10/16] minor change --- default_script.py | 0 deploy.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 default_script.py diff --git a/default_script.py b/default_script.py new file mode 100644 index 000000000..e69de29bb diff --git a/deploy.py b/deploy.py index c87fa70bc..60e0f3374 100644 --- a/deploy.py +++ b/deploy.py @@ -264,7 +264,7 @@ } environment = { - "ENVIRONMENT": { + "ENV": { "default": "dev", "help": "The environment in which the application is running. PROD : Production, DEV : Development", }, From 9f3b1047fd9b2bead49d0df86a369aaf758f0eef Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Wed, 1 May 2024 12:25:16 +0530 Subject: [PATCH 11/16] added script for defaul setup --- default-setup.py | 51 ++++++++++++++++++++++++++++++++++++ default_script.py | 0 deploy.py => docker-setup.py | 0 elk-docker-compose.yml | 6 ++--- 4 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 default-setup.py delete mode 100644 default_script.py rename deploy.py => docker-setup.py (100%) diff --git a/default-setup.py b/default-setup.py new file mode 100644 index 000000000..17a93e53e --- /dev/null +++ b/default-setup.py @@ -0,0 +1,51 @@ +import os +import subprocess +import sys + +def setup_repository(): + # Clone the repository + subprocess.run(["git", "clone", "https://github.com/AI4Bharat/Shoonya-Backend.git"]) + os.chdir("Shoonya-Backend") + # Switch to the development branch + subprocess.run(["git", "checkout", "dev"]) + subprocess.run(["git", "pull", "origin", "dev"]) + +def install_dependencies(): + # Create a virtual environment + subprocess.run(["python3", "-m", "venv", "venv"]) + # Activate the virtual environment + if sys.platform == "win32": + activate_script = "venv\\Scripts\\activate" + else: + activate_script = "venv/bin/activate" + subprocess.run(["source", activate_script]) + # Install dependencies + subprocess.run(["pip", "install", "-r", "./backend/deploy/requirements.txt"]) + +def setup_env_file(): + # Copy .env.example to .env + subprocess.run(["cp", ".env.example", "./backend/.env"]) + # Generate a new secret key + new_secret_key = subprocess.run(["python", "-c", "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"], capture_output=True, text=True) + new_secret_key = new_secret_key.stdout.strip() + # Update .env with the new secret key + with open("./backend/.env", "a") as env_file: + env_file.write(f"\nSECRET_KEY='{new_secret_key}'\n") + print("New secret key has been generated and updated in .env") + +def run_celery_instances(): + # Start Redis server + print("Please ensure Redis is installed and running on port 6379.") + # Start Celery workers + subprocess.run(["celery", "-A", "shoonya_backend.celery", "worker", "--concurrency=2", "--loglevel=info"]) + # Start Celery beat + subprocess.run(["celery", "-A", "shoonya_backend.celery", "beat", "--loglevel=info"]) + +def main(): + setup_repository() + install_dependencies() + setup_env_file() + run_celery_instances() + +if __name__ == "__main__": + main() diff --git a/default_script.py b/default_script.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/deploy.py b/docker-setup.py similarity index 100% rename from deploy.py rename to docker-setup.py diff --git a/elk-docker-compose.yml b/elk-docker-compose.yml index f04d31199..68bb52fcf 100644 --- a/elk-docker-compose.yml +++ b/elk-docker-compose.yml @@ -6,7 +6,7 @@ services: image: docker.elastic.co/elasticsearch/elasticsearch:7.14.0 volumes: - ./elasticsearch/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml - # - elasticsearch_vol:/elasticsearch_data + - elasticsearch_vol:/elasticsearch_data environment: - discovery.type=single-node ports: @@ -27,16 +27,14 @@ services: hostname: shoonya_dev_logger volumes: - ./logstash_dev.conf:/usr/share/logstash/pipeline/logstash.conf - # - logs_vol:/logs + - logs_vol:/logs extra_hosts: - "elasticsearch:elasticsearch" command: logstash -f /usr/share/logstash/pipeline/logstash.conf volumes: elasticsearch_vol: - external: true logs_vol: - external: true networks: shoonya_backend: From 169d9a6863d7b8d71dbf16b5ee140ac9044316b4 Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Wed, 1 May 2024 12:25:48 +0530 Subject: [PATCH 12/16] setup redis as part of default setup --- default-setup.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/default-setup.py b/default-setup.py index 17a93e53e..407a05047 100644 --- a/default-setup.py +++ b/default-setup.py @@ -2,14 +2,6 @@ import subprocess import sys -def setup_repository(): - # Clone the repository - subprocess.run(["git", "clone", "https://github.com/AI4Bharat/Shoonya-Backend.git"]) - os.chdir("Shoonya-Backend") - # Switch to the development branch - subprocess.run(["git", "checkout", "dev"]) - subprocess.run(["git", "pull", "origin", "dev"]) - def install_dependencies(): # Create a virtual environment subprocess.run(["python3", "-m", "venv", "venv"]) @@ -33,18 +25,21 @@ def setup_env_file(): env_file.write(f"\nSECRET_KEY='{new_secret_key}'\n") print("New secret key has been generated and updated in .env") -def run_celery_instances(): +def start_redis(): # Start Redis server - print("Please ensure Redis is installed and running on port 6379.") + print("Starting Redis server...") + subprocess.run(["redis-server"]) + +def run_celery_instances(): # Start Celery workers subprocess.run(["celery", "-A", "shoonya_backend.celery", "worker", "--concurrency=2", "--loglevel=info"]) # Start Celery beat subprocess.run(["celery", "-A", "shoonya_backend.celery", "beat", "--loglevel=info"]) def main(): - setup_repository() install_dependencies() setup_env_file() + start_redis() run_celery_instances() if __name__ == "__main__": From 0e78e0ad83d5e5c50ca2364766f934de617ed239 Mon Sep 17 00:00:00 2001 From: Amarendra Shendkar Date: Fri, 3 May 2024 12:15:12 +0530 Subject: [PATCH 13/16] fixed script --- default-setup.py | 91 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 13 deletions(-) diff --git a/default-setup.py b/default-setup.py index 407a05047..f9a60379b 100644 --- a/default-setup.py +++ b/default-setup.py @@ -1,46 +1,111 @@ import os import subprocess import sys +import time +import shutil +import string +import random +def generate_secret_key(length=50): + """ + Generate a random Django secret key. + """ + characters = string.ascii_letters + string.digits + "!@#$%^&*(-_=+)" + return ''.join(random.choice(characters) for _ in range(length)) def install_dependencies(): # Create a virtual environment subprocess.run(["python3", "-m", "venv", "venv"]) # Activate the virtual environment if sys.platform == "win32": - activate_script = "venv\\Scripts\\activate" + activate_script = os.path.join("venv", "Scripts", "activate.bat") else: - activate_script = "venv/bin/activate" - subprocess.run(["source", activate_script]) + activate_script = os.path.join("venv", "bin", "activate") + subprocess.run([activate_script]) + # Install dependencies subprocess.run(["pip", "install", "-r", "./backend/deploy/requirements.txt"]) + def setup_env_file(): # Copy .env.example to .env - subprocess.run(["cp", ".env.example", "./backend/.env"]) + shutil.copy(".env.example", "./backend/.env") + + # Generate a new secret key - new_secret_key = subprocess.run(["python", "-c", "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"], capture_output=True, text=True) - new_secret_key = new_secret_key.stdout.strip() + new_secret_key = generate_secret_key() # Update .env with the new secret key with open("./backend/.env", "a") as env_file: env_file.write(f"\nSECRET_KEY='{new_secret_key}'\n") print("New secret key has been generated and updated in .env") -def start_redis(): - # Start Redis server - print("Starting Redis server...") - subprocess.run(["redis-server"]) def run_celery_instances(): + + # Activate the virtual environment + if sys.platform == "win32": + activate_script = os.path.join("venv", "Scripts", "activate.bat") + else: + activate_script = os.path.join("venv", "bin", "activate") + subprocess.run([activate_script]) + + + + os.chdir("backend") + # Start Celery workers - subprocess.run(["celery", "-A", "shoonya_backend.celery", "worker", "--concurrency=2", "--loglevel=info"]) + celery_worker_process = subprocess.Popen( + [ + "celery", + "-A", + "shoonya_backend.celery", + "worker", + "--concurrency=2", + "--loglevel=info", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) # Start Celery beat - subprocess.run(["celery", "-A", "shoonya_backend.celery", "beat", "--loglevel=info"]) + celery_beat_process = subprocess.Popen( + ["celery", "-A", "shoonya_backend.celery", "beat", "--loglevel=info"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + # Capture and print the output + worker_output, worker_errors = celery_worker_process.communicate() + beat_output, beat_errors = celery_beat_process.communicate() + + print("Celery Worker Output:") + print(worker_output.decode("utf-8")) + print("Celery Worker Errors:") + print(worker_errors.decode("utf-8")) + + print("Celery Beat Output:") + print(beat_output.decode("utf-8")) + print("Celery Beat Errors:") + print(beat_errors.decode("utf-8")) + + +def start_django_server(): + # Activate the virtual environment + if sys.platform == "win32": + activate_script = os.path.join("venv", "Scripts", "activate.bat") + else: + activate_script = os.path.join("venv", "bin", "activate") + subprocess.run([activate_script]) + + # Start Django server + print("Starting Django server...") + subprocess.Popen(["python", "./backend/manage.py", "runserver"]) + def main(): install_dependencies() setup_env_file() - start_redis() run_celery_instances() + start_django_server() + if __name__ == "__main__": main() From 04c7577fc3e6ab6060d25b5ca99d43282931f281 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Sun, 5 May 2024 01:39:37 +0530 Subject: [PATCH 14/16] updated the readme file of documentation --- NewReadme.md | 260 ---------------------------- README.md | 421 ++++++++++++++++++++++++++++++++++++--------- oldReadme.md | 226 ++++++++++++++++++++++++ public/image-2.png | Bin 0 -> 54688 bytes public/image.png | Bin 0 -> 51199 bytes 5 files changed, 562 insertions(+), 345 deletions(-) delete mode 100644 NewReadme.md create mode 100644 oldReadme.md create mode 100644 public/image-2.png create mode 100644 public/image.png diff --git a/NewReadme.md b/NewReadme.md deleted file mode 100644 index 5a54325ec..000000000 --- a/NewReadme.md +++ /dev/null @@ -1,260 +0,0 @@ -# Shoonya Backend - -Repository for Shoonya's backend. - -## Pre-requisites - -The project was created using [Python 3.7](https://www.python.org/downloads/). All major dependencies along with the versions are listed in the `backend/deploy/requirements.txt` file. - -## Installation - - - -
- Wow, so fancy - WOW, SO BOLD -
- -# Project Name README - -## Prerequisites -- **Docker Engine/Docker Desktop running** - You may download Docker Desktop from the table given below - - | Systems| Link | - | ---------- | ------ | - | Windows | https://docs.docker.com/desktop/install/windows-install/ | - | Ubuntu | https://docs.docker.com/desktop/install/ubuntu/ | - | Unix/Mac | https://docs.docker.com/desktop/install/mac-install/| -- Python Version 3.7 or above -- An Azure account subscription. - -## Backend Setup -The whole backend setup is divided into mainly 5 Components -``` -Backend Setup -| -|-- 1. Database Setup -| -|-- 2. Default Components -| |-- a) Django -| |-- b) Celery -| |-- c) Redis -| -|-- 3. Elastic Logstash Kibana (ELK) & Flower Confirguration -| -|-- 4. Nginx-Certbot -| -|-- 5. Additional Services -| |-- a) Google Application Credentials -| |-- b) Ask_Dhruva -| |-- c) Indic_Trans_V2 -| |-- d) Email Service -| |-- e) Logging -| |-- f) Minio -``` - - -
- - 1. Database Setup - - -To set up a PostgreSQL container: -- When prompted during the script execution, enter 'Y' to include PostgreSQL installation. -- Alternatively, manually provide the following database variables in the `.env` file: - - `DB_NAME` - - `DB_USER` - - `DB_PASSWORD` - - `DB_HOST` -
- - -
- - 2. Default Components - - -This section outlines the essential setup needed for the application to function properly. It encompasses Docker deployments for Django, Celery, and Redis, which form the core components of our application infrastructure. - -#### a) Django -- **Description:** Django is a Python-based framework for web development, providing tools and features to build robust web applications quickly and efficiently. -- Configuration: - - `SECRET_KEY`: Django secret key either enter manually or generate using the command - - To create a new secret key, run the following commands (within the virtual environment): - ``` - # Open a Python shell - python backend/manage.py shell - - >> from django.core.management.utils import get_random_secret_key - >> get_random_secret_key() - ``` - -#### b) Celery -- **Description:** Celery is a system for asynchronous task processing based on distributed message passing, allowing computationally intensive operations to run in the background without impacting the main application's performance. -- Configuration: - - `CELERY_BROKER_URL`: Broker for Celery tasks - - -#### c) Redis -- Description: Redis is an open-source, in-memory data structure store used as a database, cache, and message broker. -- Configuration: - - `REDIS_HOST`: Need to configue the port - - `REDIS_PORT` : Need to configure the port - -
- -
- - 3. ELK & Flower configuration - - - -#### a) Elasticsearch-Logstash-Kibanas -This section contains the ELK stack for logging monitoring etc. - -- Elasticsearch - - Description: Elasticsearch is a distributed, RESTful search and analytics engine. - - Configuration: - - `ELASTICSEARCH_URL`: URL for Elasticsearch - - `INDEX_NAME`: Index name - -- Logstash - - Description: Logstash is a server-side data processing pipeline that ingests data from multiple sources simultaneously, transforms it, and then sends it to a "stash" like Elasticsearch. - - Configuration: None - -- Kibana - - Description: Kibana is an open-source data visualization dashboard for Elasticsearch. - - Configuration: None - - - -#### b) Flower Confirguation -- Flower is a web-based tool for monitoring and administrating Celery clusters. It allows you to keep track of tasks as they flow through your system, inspect the -system's health, and perform administrative operations -like shutting down workers. - -- Additionally, Flower would be monitoring the -tasks defined in our tasks/ directory in -the `backend/directory`. - -- Configuration: - - `FLOWER_USERNAME`: Flower username - - `FLOWER_PASSWORD`: Flower password - - `FLOWER_PORT`: Need to configure - -
- -
- - 4. Nginx-Certbot - - -This section contains Nginx and Certbot setup for serving HTTPS traffic. - -- Nginx - - Description: Nginx is a web server that can also be used as a reverse proxy, load balancer, mail proxy, and HTTP cache. - - Configuration: None - -- Certbot - - Description: Certbot is a free, open-source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS. - - Configuration: None - -
- - -
- - 5 Additional Services - - - -These are the additional services that were not only present in certain confirguration but its actually present in whole files some of them are global variables and some are services. - -#### a) Google Application Credentials -- **Description**: Google Application Credentials are used to authenticate and authorize applications to use Google Cloud APIs. They are a key part of Google Cloud's IAM (Identity and Access Management) system, and they allow the application to interact with Google's services securely. -- Parameters: - - `type` - - `project_id` - - `private_key_id` - - `private_key` - - `client_email` - - `client_id` - - `auth_uri` - - `token_uri` - - `auth_provider_x509_cert_url` - - `client_x509_cert_url` - - `universe_domain` - -#### b) Ask_Dhruva -- Description: Component for interacting with Dhruva ASR service. This service is likely used in your application to convert spoken language into written text. -- Parameters: - - `ASR_DHRUVA_URL`: Parameter is used to tell your application where to send requests for speech recognition. - - `ASR_DHRUVA_AUTHORIZATION`: Authorization token for Dhruva ASR service. - -#### c) Indic_Trans_V2 -- Description: Component for interacting with Indic Trans V2 service. -- Parameters: - - `INDIC_TRANS_V2_KEY`: API key for Indic Trans V2 service - - `INDIC_TRANS_V2_URL`: URL for Indic Trans V2 service - -#### d) Email Service -- **Description**: The Email Service is likely used in your application to send emails. This could be for a variety of purposes such as sending notifications, password resets, confirmation emails, sending reports etc. -- Parameters: - - `EMAIL_HOST` - - `SMTP_USERNAME` - - `SMTP_PASSWORD` - - `DEFAULT_FROM_EMAIL` - -#### e) Logging -- Description: -Logging is used to record events or actions that occur during the execution of your program. It's a crucial part of software development for debugging and monitoring purposes - -- Required for the application to work. Contains a Docker deployment of Django, Celery, and Redis. -- Parameters: - - `LOGGING` : This is a boolean value (either 'true' or -'false') that determines whether logging is enabled. - - `LOG_LEVEL` : This sets the level of logging. 'INFO' will -log all INFO, WARNING, ERROR, and CRITICAL level -logs. - -#### f) MINIO -- Description: MinIO is an open-source, high-performance, AWS S3 compatible object storage system. It is typically used in applications for storing unstructured data like photos, videos, log files, backups, and container/VM images. -- Parameters: - - `MINIO_ACCESS_KEY` - - `MINIO_SECRET_KEY` - - `MINIO_ENDPOINT` - -
- -## Running the Setup Script - -To run the setup script: -1. Clone this repository to your local machine. - -```bash -git clone "https://github.com/AI4Bharat/Shoonya-Backend" -``` -2. Navigate to the root directory of the project. -```bash -cd Shoonya_Backend -``` -3. Run the following command: `python deploy.py` make sure the docker engine is running on your system -4. Provide the details that has been asking in the prompt and it will automatically create & run the docker containers, volumnes and processes - - - - -## What the script does? -- Automatically creates a Docker network named `shoonya_backend`. -- Prompts the user to choose whether to run the application in production mode. -- Guides the user through setting up a PostgreSQL container if desired. -- Allows selection of components and sets up Docker Compose files accordingly. -- Manages environment variables in the `.env` file for each selected component. -- Deploys Docker containers for selected components. -- Provides feedback and error handling throughout the setup process. - - - - diff --git a/README.md b/README.md index 5281a3da1..c555e5553 100644 --- a/README.md +++ b/README.md @@ -6,138 +6,389 @@ Repository for Shoonya's backend. The project was created using [Python 3.7](https://www.python.org/downloads/). All major dependencies along with the versions are listed in the `backend/deploy/requirements.txt` file. -## Installation +## Setup and Installation -The installation and setup instructions have been tested on the following platforms: +Please follow the below instructions for setup and installation. These steps have been verified on the following operating systems: -It can be done in 2 ways:- -1) using docker by running all services in containers or -2) directly running the services on local one by one (like, celery and redis). - -If your preference is 1 over 2 please be fixed to this environment and follow the steps below it: -- Docker -- Ubuntu 20.04 OR macOs +| Systems | Requirements | +| --------- | ------------ | +| Windows | Windows 10 or later with at least 8GB of RAM. | +| Ubuntu | Ubuntu 20.04 or later with at least 4GB of RAM. | +| Unix/Mac | macOS 10.13 or later with at least 4GB of RAM. | -If you are using a different operating system, you will have to look at external resources (eg. StackOverflow) to correct any errors. + +The installation can be done in three ways, each with its own advantages: + +1. **Instruction-based prompt Interaction**: This is the latest, easiest, and most hassle-free installation method. All services will be run under Docker containers and volumes, ensuring a consistent environment and simplifying the setup process. + +2. **Using Docker by running all services in containers**: This method also uses Docker, but requires running a single compose file. While this method is also straightforward, it might throw errors if the compose file is not correctly configured or if there are issues with the Docker setup. + +3. **Traditional deployment using Django manage.py runserver default script**: This method involves running the services locally one by one (like Celery and Redis). While this method gives you the most control and visibility into each service, it is more complex and time-consuming. Also, asynchronous tasks won't work under Celery tasks in this setup. + +## Prerequisites ### Create a Virtual Environment We recommend you to create a virtual environment to install all the dependencies required for the project. +For **Ubuntu/Mac**: + ```bash python3 -m venv -source /bin/activate # this command may be different based on your OS - -# Install dependencies -pip install -r deploy/requirements-dev.txt +source /bin/activate +``` +For **Windows**: +```bash +python -m venv +\Scripts\activate ``` -### Environment file +If you are choosing 1st or 2nd method then you need to install Docker on your system + +- **Docker Engine/Docker Desktop running** + You may download Docker Desktop from the table given below + + | Systems| Link | + | ---------- | ------ | + | Windows | https://docs.docker.com/desktop/install/windows-install/ | + | Ubuntu | https://docs.docker.com/desktop/install/ubuntu/ | + | Unix/Mac | https://docs.docker.com/desktop/install/mac-install/| +- Python Version 3.7 or above +- An Azure account subscription. + +## Running the Setup Script + +To run the setup script: +1. Clone this repository to your local machine. -To set up the environment variables needed for the project, run the following lines: ```bash -cp .env.example ./backend/.env +git clone "https://github.com/AI4Bharat/Shoonya-Backend" +``` +2. Navigate to the root directory of the project. +```bash +cd Shoonya-Backend +pip install -r ./backend/deploy/requirements.txt ``` -This creates an `.env` file at the root of the project. It is needed to make sure that the project runs correctly. Please go through the file and set the parameters according to your installation. +Now choosing the best option for Installation method (mentioned above) It is important to know brief about them -To create a new secret key, run the following commands (within the virtual environment): -```bash -# Open a Python shell -python backend/manage.py shell +> Note : You may skip this section if you are already familiar with the concepts, but reviewing it could enhance your understanding of the backend setup. ->> from django.core.management.utils import get_random_secret_key ->> get_random_secret_key() +## Backend Components +The whole backend setup is divided into mainly 5 Components +``` +Backend Setup +| +|-- 1. Database Setup +| +|-- 2. Default Components +| |-- a) Django +| |-- b) Celery +| |-- c) Redis +| +|-- 3. Elastic Logstash Kibana (ELK) & Flower Confirguration +| +|-- 4. Nginx-Certbot +| +|-- 5. Additional Services +| |-- a) Google Application Credentials +| |-- b) Ask_Dhruva +| |-- c) Indic_Trans_V2 +| |-- d) Email Service +| |-- e) Logging +| |-- f) Minio ``` -Paste the value you get there into the `.env` file. +> Note : There are some accordian so you need to expand -#### Google Cloud Logging (Optional) -If Google Cloud Logging is being used, please follow these additional steps: +
+ + 1. Database Setup + -1. Install the `google-cloud-logging` library using the following command: -```bash -pip install google-cloud-logging -``` -2. Follow the steps to create a Service Account from the following [Google Cloud Documentation Page](https://cloud.google.com/docs/authentication/production#create_service_account). This will create a Service Account and generate a JSON Key for the Service Account. -3. Ensure that atleast the Project Logs Writer role (`roles/logging.logWriter`) is assigned to the created Service Account. -4. Add the `GOOGLE_APPLICATION_CREDENTIALS` variable to the `.env` file. This value of this variable should be the path to the JSON Key generated in Step 2. For example, +To set up a PostgreSQL container: +- When prompted during the script execution, enter 'Y' to include PostgreSQL installation. +- Alternatively, manually provide the following database variables in the `.env` file: + - `DB_NAME` + - `DB_USER` + - `DB_PASSWORD` + - `DB_HOST` +
-```bash -GOOGLE_APPLICATION_CREDENTIALS="/path/to/gcloud-key.json" -``` +
+ + 2. Default Components + -### Docker Installation +This section outlines the essential setup needed for the application to function properly. It encompasses Docker deployments for Django, Celery, and Redis, which form the core components of our application infrastructure. +#### a) Django +- **Description:** Django is a Python-based framework for web development, providing tools and features to build robust web applications quickly and efficiently. +- Configuration: + - `SECRET_KEY`: Django secret key either enter manually or generate using the command + To create a new secret key, run the following commands (within the virtual environment): + ``` + # Open a Python shell + python backend/manage.py shell + >> from django.core.management.utils import get_random_secret_key + >> get_random_secret_key() + ``` -```markdown -# Application Setup Guide +#### b) Celery +- **Description:** Celery is a system for asynchronous task processing based on distributed message passing, allowing computationally intensive operations to run in the background without impacting the main application's performance. +- Configuration: + - `CELERY_BROKER_URL`: Broker for Celery tasks + -This guide will walk you through setting up the application using different methods and components. +#### c) Redis +- Description: Redis is an open-source, in-memory data structure store used as a database, cache, and message broker. +- Configuration: + - `REDIS_HOST`: Need to configue the port + - `REDIS_PORT` : Need to configure the port -## Running the Script +
+ +
+ + 3. ELK & Flower configuration + + + +#### a) Elasticsearch-Logstash-Kibanas +This section contains the ELK stack for logging monitoring etc. + +- Elasticsearch + - Description: Elasticsearch is a distributed, RESTful search and analytics engine. + - Configuration: + - `ELASTICSEARCH_URL`: URL for Elasticsearch + - `INDEX_NAME`: Index name + +- Logstash + - Description: Logstash is a server-side data processing pipeline that ingests data from multiple sources simultaneously, transforms it, and then sends it to a "stash" like Elasticsearch. + - Configuration: None + +- Kibana + - Description: Kibana is an open-source data visualization dashboard for Elasticsearch. + - Configuration: None + + + +#### b) Flower Confirguation +- Flower is a web-based tool for monitoring and administrating Celery clusters. It allows you to keep track of tasks as they flow through your system, inspect the +system's health, and perform administrative operations +like shutting down workers. + +- Additionally, Flower would be monitoring the +tasks defined in our tasks/ directory in +the `backend/directory`. + +- Configuration: + - `FLOWER_USERNAME`: Flower username + - `FLOWER_PASSWORD`: Flower password + - `FLOWER_PORT`: Need to configure + +
-To run the setup script, follow these steps: +
+ + 4. Nginx-Certbot + -1. Clone this repository to your local machine. -2. Navigate to the repository directory. -3. Run the following command: +This section contains Nginx and Certbot setup for serving HTTPS traffic. - ```bash - python setup.py - ``` +- Nginx + - Description: Nginx is a web server that can also be used as a reverse proxy, load balancer, mail proxy, and HTTP cache. + - Configuration: None -4. Follow the prompts to select components and provide required parameters. +- Certbot + - Description: Certbot is a free, open-source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS. + - Configuration: None -## Components +
-### Default Setup +
+ + 5 Additional Services + -- **Description:** Required for the application to work. Contains a docker deployment of Django, Celery, and Redis. - - **Parameters:** - - `DB_NAME` - - **Description:** Database name. - - **Default:** `postgres` - - `DB_USER` - - **Description:** Database user. - - **Default:** `postgres` - - `DB_PASSWORD` - - **Description:** Database password. - - **Default:** `postgres` - - `DB_HOST` - - **Description:** Database host. - - **Default:** `db` - - `SECRET_KEY` - - **Description:** Django secret key. - - **Default:** `abcd1234` - - `AZURE_CONNECTION_STRING` - - **Description:** Azure storage string. - - **Default:** `AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net` - - `LOGS_CONTAINER_NAME` - - **Description:** Logs container name. - - **Default:** `logs` +These are the additional services that were not only present in certain confirguration but its actually present in whole files some of them are global variables and some are services. + +#### a) Google Application Credentials +- **Description**: Google Application Credentials are used to authenticate and authorize applications to use Google Cloud APIs. They are a key part of Google Cloud's IAM (Identity and Access Management) system, and they allow the application to interact with Google's services securely. +- Parameters: + - `type` + - `project_id` + - `private_key_id` + - `private_key` + - `client_email` + - `client_id` + - `auth_uri` + - `token_uri` + - `auth_provider_x509_cert_url` + - `client_x509_cert_url` + - `universe_domain` + +#### b) Ask_Dhruva +- Description: Component for interacting with Dhruva ASR service. This service is likely used in your application to convert spoken language into written text. +- Parameters: + - `ASR_DHRUVA_URL`: Parameter is used to tell your application where to send requests for speech recognition. + - `ASR_DHRUVA_AUTHORIZATION`: Authorization token for Dhruva ASR service. +#### c) Indic_Trans_V2 +- Description: Component for interacting with Indic Trans V2 service. +- Parameters: + - `INDIC_TRANS_V2_KEY`: API key for Indic Trans V2 service + - `INDIC_TRANS_V2_URL`: URL for Indic Trans V2 service +#### d) Email Service +- **Description**: The Email Service is likely used in your application to send emails. This could be for a variety of purposes such as sending notifications, password resets, confirmation emails, sending reports etc. +- Parameters: + - `EMAIL_HOST` + - `SMTP_USERNAME` + - `SMTP_PASSWORD` + - `DEFAULT_FROM_EMAIL` - +``` @@ -206,7 +456,7 @@ celery command - celery -A shoonya_backend.celery worker -l info celery command - celery -A shoonya_backend.celery beat --loglevel=info ``` -You can set use the celery to local by modifying CELERY_BROKER_URL = "redis://localhost:6379/0" in ./backend/shoonya_backend/settings.py. +You can set use the celery to local by modifying `CELERY_BROKER_URL = "redis://localhost:6379/0"` in ./backend/shoonya_backend/settings.py. We can set the concurrency and autoscale in the process as well to manage the number of worker processes in the background. Read more [here](https://stackoverflow.com/a/72366865/9757174). @@ -226,3 +476,4 @@ black ./backend/ ``` Happy Coding!! + diff --git a/oldReadme.md b/oldReadme.md new file mode 100644 index 000000000..4889df792 --- /dev/null +++ b/oldReadme.md @@ -0,0 +1,226 @@ +# Shoonya Backend + +Repository for Shoonya's backend. + +## Pre-requisites + +The project was created using [Python 3.7](https://www.python.org/downloads/). All major dependencies along with the versions are listed in the `backend/deploy/requirements.txt` file. + +## Installation + +The installation and setup instructions have been tested on the following platforms: + +It can be done in 2 ways:- +1) using docker by running all services in containers or +2) directly running the services on local one by one (like, celery and redis). + +If your preference is 1 over 2 please be fixed to this environment and follow the steps below it: + +- Docker +- Ubuntu 20.04 OR macOs + +If you are using a different operating system, you will have to look at external resources (eg. StackOverflow) to correct any errors. + +### Create a Virtual Environment + +We recommend you to create a virtual environment to install all the dependencies required for the project. + +```bash +python3 -m venv +source /bin/activate # this command may be different based on your OS + +# Install dependencies +pip install -r deploy/requirements-dev.txt +``` + +### Environment file + +To set up the environment variables needed for the project, run the following lines: +```bash +cp .env.example ./backend/.env +``` + +This creates an `.env` file at the root of the project. It is needed to make sure that the project runs correctly. Please go through the file and set the parameters according to your installation. + +To create a new secret key, run the following commands (within the virtual environment): +```bash +# Open a Python shell +python backend/manage.py shell + +>> from django.core.management.utils import get_random_secret_key +>> get_random_secret_key() +``` + +Paste the value you get there into the `.env` file. + +#### Google Cloud Logging (Optional) + +If Google Cloud Logging is being used, please follow these additional steps: + +1. Install the `google-cloud-logging` library using the following command: +```bash +pip install google-cloud-logging +``` +2. Follow the steps to create a Service Account from the following [Google Cloud Documentation Page](https://cloud.google.com/docs/authentication/production#create_service_account). This will create a Service Account and generate a JSON Key for the Service Account. +3. Ensure that atleast the Project Logs Writer role (`roles/logging.logWriter`) is assigned to the created Service Account. +4. Add the `GOOGLE_APPLICATION_CREDENTIALS` variable to the `.env` file. This value of this variable should be the path to the JSON Key generated in Step 2. For example, + +```bash +GOOGLE_APPLICATION_CREDENTIALS="/path/to/gcloud-key.json" +``` + +### Docker Installation + + +```markdown +# Application Setup Guide + +This guide will walk you through setting up the application using different methods and components. + +## Running the Script + +To run the setup script, follow these steps: + +1. Clone this repository to your local machine. +2. Navigate to the repository directory. +3. Run the following command: + + ```bash + python setup.py + ``` + +4. Follow the prompts to select components and provide required parameters. + +## Components + +### Default Setup + +- **Description:** Required for the application to work. Contains a docker deployment of Django, Celery, and Redis. + + **Parameters:** + - `DB_NAME` + - **Description:** Database name. + - **Default:** `postgres` + - `DB_USER` + - **Description:** Database user. + - **Default:** `postgres` + - `DB_PASSWORD` + - **Description:** Database password. + - **Default:** `postgres` + - `DB_HOST` + - **Description:** Database host. + - **Default:** `db` + - `SECRET_KEY` + - **Description:** Django secret key. + - **Default:** `abcd1234` + - `AZURE_CONNECTION_STRING` + - **Description:** Azure storage string. + - **Default:** `AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net` + - `LOGS_CONTAINER_NAME` + - **Description:** Logs container name. + - **Default:** `logs` + + + + + + + + +If there were no errors, congratulations! The project is up and running. + +If your preference is 2 over 1 please be fixed to this environment and follow the steps below it: + +- Ubuntu 20.04 OR macOs + +You can run the following script and each and every step for setting the codebase will be done directly. Please move to a folder in your local where you would like to store the code and run the script given below there: +```bash +os=$(uname) + +if [ "$os" = "Linux" ] || [ "$os" = "Darwin" ]; then + +git clone https://github.com/AI4Bharat/Shoonya-Backend.git +cd Shoonya-Backend +git checkout dev +git pull origin dev +cp .env.example ./backend/.env +cd backend +python3 -m venv venv +source venv/bin/activate + +pip install -r ./deploy/requirements.txt + +new_secret_key=$(python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())") + +env_file=".env" +if sed --version 2>&1 | grep -q 'GNU sed'; then + sed -i "/^SECRET_KEY=/d" "$env_file" +else + sed -i.bak "/^SECRET_KEY=/d" "$env_file" + rm -f "$env_file.bak" +fi + +echo "SECRET_KEY='$new_secret_key'" >> "$env_file" + +echo "New secret key has been generated and updated in $env_file" + +else + echo "Cannot run this script on: $os" +fi + ``` + +### Running background tasks +Please install and run redis from https://redis.io/download/ on port 6379 before starting celery. + +To run background tasks for project creation, we need to run the following command in the terminal. This has also been added into the `docker-compose.yml` file. +```bash +celery command - celery -A shoonya_backend.celery worker -l info +celery command - celery -A shoonya_backend.celery beat --loglevel=info +``` + +You can set use the celery to local by modifying CELERY_BROKER_URL = "redis://localhost:6379/0" in ./backend/shoonya_backend/settings.py. + +We can set the concurrency and autoscale in the process as well to manage the number of worker processes in the background. Read more [here](https://stackoverflow.com/a/72366865/9757174). + +The commands will be as follows +```bash +celery -A shoonya_backend.celery worker --concurrency=2 --loglevel=info +celery -A shoonya_backend.celery worker --autoscale=10,3 --loglevel=info +``` + +### Running Linters + +In case you want to raise a PR, kindly run linters as specified below. You can install black by running pip install black and use `black` +To run `black` do: + +```bash +black ./backend/ +``` + +Happy Coding!! diff --git a/public/image-2.png b/public/image-2.png new file mode 100644 index 0000000000000000000000000000000000000000..644d35d9a22bc0ef095dfec86633e56e581d4c22 GIT binary patch literal 54688 zcmcGW1yo#1)2?v|4#64RJ-8E`5Zv9}o#4UUEkJO03pQAAmkI7J!QJI@zT|x8ocqhQ zb@y7#UOls!+0)fsRsHl^6DBV!f%FFN4Hy_0(g#UVMKCZJWH2yrYB-qJXFmJBL3%xb zJ1I&CgH?_Z{CsUdnF+}Vfq~V;AUqmCzqaA+B{iJDz>vHD_yg~^D>eoLn`ZwYDx~~L z_wWftURf=Z^SJ}uB9KywO3KjuB^ySYgwe*7L`mLMKUW!1l{6u7M`1@}fD`f|awOrs z4D-SJ(eERrC6-hjYmbCqyn5%w=!`i#r45!a&Gcm~Ox9ARTi}7hl$~K;j6cswnJ4b7 z-eh{pi9jMbH?oDd>Zzz5^Lw{?->*2`IX!ztuhyCj2fr8n>&QP%(F+Ux>zH%+`>SOC zYESc|K1=1gAr&E07=3!(`d9BbTkT9lLIV7;t2#>o zunwz`9;vRoxR!&xL9>}(p)+7&4A)uvQ7=X|n|#kCH`_fkzqhRdiJluG7UFfC(4e z6f2ZSZ8MW}N7eUJo2cHLGlI|Jl#xvZ7wG@~`XGjMURGq_O5be93l3+Z&I|O%dN!dz zc8@cDoY|pG1AKeE8PCvR+T{K>>?c}Hb`ChsC@F?ljN2}tcjp`L%6&FYOehBvTBC0i z^-|I=9(XSHTD!X8WNAb$?c3iiik`6IcFUcv(E?yv1bQSexo_{gPPP$_nGJI+kRa}v zHkgu4Qz@5!xc@bT%y~vCdgFGXN#8G{v7)qMhWE;)7>&BAkvellMkD8d#y4^jk#Uax@lD?(3L)c$>9J?xQxQdly}+r2WpWgi1th;54XTBw0JW? zMz_JO4^1w*o2%XCQw9^)xB?YJGh;8lQWK7y;X>Oc=_Z0>DxX#)iCS-cwpTkg#9uzD z*KRWK(VH=qFJ57(3Y>{BOhO4uTQTe1b?7~<`lis3{lmy?{DqC1-)jo}j26$}rc6Sk zh)WAT{2Z=7agQ|EZN-wDz78> zK@9KGsNDJ2XWX#^4cpHPcDoF`E>zN!XC7cLADRFM zLKIVXf((T&P0JsihgnP_vxyAPUQH(8O*C81GMd`BOF{bQk!!(PB#f!^(m~O5dW?TQ z;5o}sHN+p{x^x)VV)Q9~7Q;59akg?y7*3-FG6yCv8S9c-nA7gNu*vMobY+42pwAWQ zYquq^Qe^h{syrdj=oTn0KfqxPH#!x5Tmpjxo+X()<_?VU@iYB6wDWW$m$9~6_1J9VwI({6U8&k&Zr_8zde#Atv}z+T5ybbtx6-~>Y#zL4 zSAP;5uAj$#kq@0N6JB3^z-63zRyDosGts_H@$$Nu9)Rc<(R)JAdRea;(xvvtGPfXm zCXAR3Hay)A*^5me(!RL-+eBo`*y)Q5sB8r3lJUHAR!^+`NEwaQdaKY}1h4)Kb)0b& zH5HN73;FSLJ0qQKu|XshudUY|Nm|Tta(`x>-jV8a8_~z)GQVPRcsK`u|KRu~^>Mmc z|E%wAk~Hpk`rl{fWg2fzI8V6!wh}M19mR~bhcG3rF&EKOX%TAcdp33a9M7h=WsQ}S zGiG0UolykKy@G9>zplgxzC@5+j;peDs08Bc>}#V|&1ngA%j{KuniN>N_)O*+i*})U zB(}ARoA@+r-jZElIn#Z5s(8D|xFI#%b8>L^_o)aREn!FY@OcsFNo&2B6)2;C%kueL z);2T?eSn&ARTb)4Ltxd>g5D`LcFf2SFkZJlUSBVVl->12IdBDZ*7g(`%c9gj!PwZi-X5qKPP^hld}y|<(GoR_0A$p6B;pnn z145gy_u5bGSPURCGgJNwF@+g+yvPJrgd-DaFiQJ(6zz|a2q74C+qeu!cAG;rtLv87 z%+yM@;qn5Q^S73TJjhw#egk zR7ujQ39}~UdVCvf@fj*qRv4_b8RG00(r2%0ITjnz%&&jD2d_@M`C1bZM_)@rjm#W+Xgr5l|PFkXcxGK}r#RAHow@_0hCxd7IvX;4HW5uSv`31~n z`HzwZpIHVth7-k`U7$=&oP1R{3DyTn_AtJ9__{ruVtUbBX@|apkD|_;tQ)4i$m+h$ zs)QK7Tu~gStz3U+tNo=w{aeWWPL)6cVD@n{1wGol=diqnN|Yt&eYv$tlF3~9QEBQN zrylx4c60XFXp<=2|x4{TANyuMQ?Q1$1Nde9G+t$L@m<;=LZ+Y&O{8MNY2l8@k^sg2QzM$|{`a0wJ)(>Las`Q8K)oOqoc4oP5783Ta^;9|n>ebvC%qdhU z1IW`XLccla(gki7n@Ua%K(+B zTNK94gS%?skEx~PZOyqe2o131d3@xe-_f4x#UiESR@`aqUhpD-m5N?#{lvtsm;cEC zy`uQTc)x-BTl6}34c^H(RBjfLE?X;!gH)+jMIY6V@)Wt%EJT#M^Nbt_Be)dyV+D?l zt+MEKH>6}Vc5w%=iSWJh!LK-N}+#&mnjtfj)%VdKQzFZIW{Kv;m4U$q5qj&iM)-iAjM-_cw>~ zKEV*3c0|387%ZTqQG9)G(-OWf1131ND=81*u!wt}jD&4giB@e?s@5-; zbqdJmo^#LqE+E+?Ic**n7G&ixSwnoDLIhm40P7)Rm&&mET)27cj7OeV{S68iK2PM$ z&r9(yEK>Hx%o>GUduT~Y;g8A^WUbi&Bs_qYi?%4=#L&lOZb=7RRT?6bmR#d-&LyUk znYJXU_Ba@OA+u0XOCQ{kR5X^}57}tY8w=ccH_#5twdb+eR55MqJMR26c7@GSJ1)TQ zllliMv|IU+G_RJJyRz6EA_!MWB?zLy`WHpc`a zv=@2pfI4a~Pd?0^56MCrNIK&Uc>nUJzz7Y!-d%|Z`#dXPAzssN-}LxaCZ+MPz@y9$ zywGhF0^fR={S;Ee3uJdT&r$P!p%sRWVelZOy^sMsFjs>pP(!HrvpDw?3i_u zpKGPdH#4arSR^Ke`~Y%<+KaRA=>}9F)5kub)3+}$_&WRhr zst|6NG4Cf^9I%G$w5`i+u7r3v|K=1vst|lS$F{A+{Z;Hk@Fs!jz@(hz&^fJe!*Oqe zd0Z7Pd%-zRD9aRE_ZtAIQf!st8E5%RnY>b;$cXv^@ z>Oz?;B%?rddW#B;9dP}4Rpx`Uj5*J1G4}Z%*q(#QXx%UOTs$9e6L)Cu#x2(E#{lIa z3zZdfAJMWkWYCdd`Nn47>CQETLI(C~7S^i<{^S5B{NM-=wPx zEB3UmW?%kcusr7TAZU0^-%dhA1KCk3T8Q6e+&z)g;zg~hBM804L04|5W%O;uR((#% zh6Il~V>EU;elp5v8EA!5anbP~uR;_OmDoy+LPPglC)%`wf=;|4bWkQ5 zhXU=+GrO+>2KSb!eXiZ*8h6S4OWT8AX331MpUM(%(0Kh8#!nRPd2*P%u}QX%Sirx` z-ap3CJ3`q04GW0Jt)y{j55K`g>Udo03#FUfTX($w_bprx?m^harnc|Sbh z=G{?s$jX-#|H~4`uE0cLdOe6afqQly@hEj$kt%ZuL>5x(X�T?=GkD_qguE`O?}7 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 literal 0 HcmV?d00001 diff --git a/public/image.png b/public/image.png new file mode 100644 index 0000000000000000000000000000000000000000..78b8c53782d9f473a4d5edc59ed4c730e739a044 GIT binary patch literal 51199 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 literal 0 HcmV?d00001 From 4f32bf31a7581962954a5833d61c08be5f93c6b9 Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Mon, 6 May 2024 10:46:58 +0530 Subject: [PATCH 15/16] updated black linting on docker-setup.py --- default-setup.py | 11 +++++------ docker-setup.py | 2 -- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/default-setup.py b/default-setup.py index f9a60379b..9edd27ce3 100644 --- a/default-setup.py +++ b/default-setup.py @@ -5,12 +5,15 @@ import shutil import string import random + + def generate_secret_key(length=50): """ Generate a random Django secret key. """ characters = string.ascii_letters + string.digits + "!@#$%^&*(-_=+)" - return ''.join(random.choice(characters) for _ in range(length)) + return "".join(random.choice(characters) for _ in range(length)) + def install_dependencies(): # Create a virtual environment @@ -29,7 +32,6 @@ def install_dependencies(): def setup_env_file(): # Copy .env.example to .env shutil.copy(".env.example", "./backend/.env") - # Generate a new secret key new_secret_key = generate_secret_key() @@ -40,7 +42,6 @@ def setup_env_file(): def run_celery_instances(): - # Activate the virtual environment if sys.platform == "win32": activate_script = os.path.join("venv", "Scripts", "activate.bat") @@ -48,10 +49,8 @@ def run_celery_instances(): activate_script = os.path.join("venv", "bin", "activate") subprocess.run([activate_script]) - - os.chdir("backend") - + # Start Celery workers celery_worker_process = subprocess.Popen( [ diff --git a/docker-setup.py b/docker-setup.py index 78f94194e..75edcac5b 100644 --- a/docker-setup.py +++ b/docker-setup.py @@ -271,12 +271,10 @@ "AZURE_CONNECTION_STRING": { "help": "AZURE storage string", "default": "AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=dummydeveloper;AccountKey=hello/Jm+uq4gvGgd5aloGrqVxYnRs/dgPHX0G6U4XmLCtZCIeKyNNK0n3Q9oRDNE+AStMDbqXg==;EndpointSuffix=core.windows.net", - }, "LOGS_CONTAINER_NAME": { "help": "Logs container name", "default": "logs", - }, } From b9e885ed86cc1aac7f490f98a54005d0a62e1bfd Mon Sep 17 00:00:00 2001 From: Pursottam6003 Date: Tue, 7 May 2024 00:09:20 +0530 Subject: [PATCH 16/16] updated readme.md file based on the review --- README.md | 214 +++++++++++++++++++----------------------------------- 1 file changed, 74 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index c555e5553..32cd76f34 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,6 @@ Repository for Shoonya's backend. The project was created using [Python 3.7](https://www.python.org/downloads/). All major dependencies along with the versions are listed in the `backend/deploy/requirements.txt` file. -## Setup and Installation - -Please follow the below instructions for setup and installation. These steps have been verified on the following operating systems: - | Systems | Requirements | | --------- | ------------ | @@ -18,80 +14,22 @@ Please follow the below instructions for setup and installation. These steps hav | Unix/Mac | macOS 10.13 or later with at least 4GB of RAM. | -The installation can be done in three ways, each with its own advantages: - -1. **Instruction-based prompt Interaction**: This is the latest, easiest, and most hassle-free installation method. All services will be run under Docker containers and volumes, ensuring a consistent environment and simplifying the setup process. - -2. **Using Docker by running all services in containers**: This method also uses Docker, but requires running a single compose file. While this method is also straightforward, it might throw errors if the compose file is not correctly configured or if there are issues with the Docker setup. - -3. **Traditional deployment using Django manage.py runserver default script**: This method involves running the services locally one by one (like Celery and Redis). While this method gives you the most control and visibility into each service, it is more complex and time-consuming. Also, asynchronous tasks won't work under Celery tasks in this setup. - -## Prerequisites - -### Create a Virtual Environment - -We recommend you to create a virtual environment to install all the dependencies required for the project. - -For **Ubuntu/Mac**: - -```bash -python3 -m venv -source /bin/activate -``` -For **Windows**: -```bash -python -m venv -\Scripts\activate -``` - -If you are choosing 1st or 2nd method then you need to install Docker on your system - -- **Docker Engine/Docker Desktop running** - You may download Docker Desktop from the table given below - - | Systems| Link | - | ---------- | ------ | - | Windows | https://docs.docker.com/desktop/install/windows-install/ | - | Ubuntu | https://docs.docker.com/desktop/install/ubuntu/ | - | Unix/Mac | https://docs.docker.com/desktop/install/mac-install/| -- Python Version 3.7 or above -- An Azure account subscription. - -## Running the Setup Script - -To run the setup script: -1. Clone this repository to your local machine. - -```bash -git clone "https://github.com/AI4Bharat/Shoonya-Backend" -``` -2. Navigate to the root directory of the project. -```bash -cd Shoonya-Backend -pip install -r ./backend/deploy/requirements.txt -``` - -Now choosing the best option for Installation method (mentioned above) It is important to know brief about them - -> Note : You may skip this section if you are already familiar with the concepts, but reviewing it could enhance your understanding of the backend setup. -## Backend Components +## Backend Components and Services The whole backend setup is divided into mainly 5 Components ``` -Backend Setup -| -|-- 1. Database Setup +Backend Comopnents | -|-- 2. Default Components +|-- 1. Default Components | |-- a) Django | |-- b) Celery | |-- c) Redis | -|-- 3. Elastic Logstash Kibana (ELK) & Flower Confirguration +|-- 2. Elastic Logstash Kibana (ELK) & Flower Confirguration | -|-- 4. Nginx-Certbot +|-- 3. Nginx-Certbot | -|-- 5. Additional Services +|-- 4. Additional Services | |-- a) Google Application Credentials | |-- b) Ask_Dhruva | |-- c) Indic_Trans_V2 @@ -102,26 +40,10 @@ Backend Setup > Note : There are some accordian so you need to expand -

- 1. Database Setup - - -To set up a PostgreSQL container: -- When prompted during the script execution, enter 'Y' to include PostgreSQL installation. -- Alternatively, manually provide the following database variables in the `.env` file: - - `DB_NAME` - - `DB_USER` - - `DB_PASSWORD` - - `DB_HOST` -
- -
- - 2. Default Components + 1. Default Components This section outlines the essential setup needed for the application to function properly. It encompasses Docker deployments for Django, Celery, and Redis, which form the core components of our application infrastructure. @@ -157,7 +79,7 @@ This section outlines the essential setup needed for the application to function
- 3. ELK & Flower configuration + 2. ELK & Flower configuration @@ -199,7 +121,7 @@ the `backend/directory`.
- 4. Nginx-Certbot + 3. Nginx-Certbot This section contains Nginx and Certbot setup for serving HTTPS traffic. @@ -217,7 +139,7 @@ This section contains Nginx and Certbot setup for serving HTTPS traffic.
- 5 Additional Services + 4 Additional Services @@ -279,21 +201,48 @@ logs.
-### Now again resuming our setup installation +## Setup Instructions -
- -Method 1 : Instruction-based prompt Interaction - +The installation can be done in two ways, each with its own advantages: -Hope you have completed step 1 and 2 above +1. **Dockerize Installation**: This is the latest, easiest, and most hassle-free installation method. All services will be run under Docker containers and volumes, ensuring a consistent environment and simplifying the setup process. -3. Run the following command: `python deploy.py` make sure the docker engine is running on your system +2. **Default Installation (Without Docker)**: This method involves running the services locally one by one (like Celery and Redis). While this method gives you the most control and visibility into each service, it is more complex and time-consuming. Also, asynchronous tasks won't work under Celery tasks in this setup. -4. Provide the details that has been asking in the prompt and it will automatically create & run the docker containers, volumnes and processes +### 1. Dockerize Installation -3. Run the following command: `python deploy.py` make sure the docker engine is running on your system +### Pre-requisites + +If you are choosing dockerize method then you need to install following things + +- **Docker Engine/Docker Desktop running** + You may download Docker Desktop from the table given below + + | Systems| Link | + | ---------- | ------ | + | Windows | https://docs.docker.com/desktop/install/windows-install/ | + | Ubuntu | https://docs.docker.com/desktop/install/ubuntu/ | + | Unix/Mac | https://docs.docker.com/desktop/install/mac-install/| +- Python Version 3.7 or above +- An Azure account subscription. +- Google cloud subscriptions (mentioned above the backend components) + + +### Running the Setup Script + +To run the setup script: +1. Clone this repository to your local machine. + + ```bash + git clone "https://github.com/AI4Bharat/Shoonya-Backend" + ``` +2. Navigate to the root directory of the project. + ```bash + cd Shoonya-Backend + pip install -r ./backend/deploy/requirements.txt + ``` + +3. Run the following command: `python docker-setup.py` make sure the docker engine is running on your system ![installation image](public/image.png) @@ -302,7 +251,7 @@ Hope you have completed step 1 and 2 above 5. Once everything has been asked it will start creating containers and volumes and the server will get started on `http://localhost:8000` all the respective services will run on the provided ports -#### What the script does? +### What the script does? - Automatically creates a Docker network named `shoonya_backend`. - Prompts the user to choose whether to run the application in production mode. - Guides the user through setting up a PostgreSQL container if desired. @@ -311,53 +260,41 @@ Hope you have completed step 1 and 2 above - Deploys Docker containers for selected components. - Provides feedback and error handling throughout the setup process. -
-
- -Method 2 : Using Docker by running all services in containers - +### 2. Default Installation (Without Docker) -3. To set up the environment variables needed for the project, run the following lines: +### Pre-requisites - ```bash - cp .env.example ./backend/.env - ``` -4. Optional : Create a new secret key, run the following commands (within the virtual environment): - ```bash - # Open a Python shell - python backend/manage.py shell +#### Create a Virtual Environment - from django.core.management.utils import get_random_secret_key - get_random_secret_key()``` - - Paste the value you get there into the `.env` file. - - This creates an `.env` file at the root of the project. It is needed to make sure that the project runs correctly. Please go through the file and set the parameters according to your installation. - -5. `cd` back to the root folder .Once inside, build the docker +We recommend you to create a virtual environment to install all the dependencies required for the project. - ```bash - docker-compose -f docker-compose-local.yml build - ``` +For **Ubuntu/Mac**: - To run the containers: +```bash +python3 -m venv +source /bin/activate +``` +For **Windows**: +```bash +python -m venv +\Scripts\activate +``` - ```bash - docker-compose -f docker-compose-local.yml up -d - ``` +#### Running the Setup Script - To share the database with others, just share the postgres_data and the media folder with others. -
+ 1. Clone this repository to your local machine. -
- -Method 3 : Traditional deployment using Django manage.py runserver default script - + ```bash + git clone "https://github.com/AI4Bharat/Shoonya-Backend" + ``` + 2. Navigate to the root directory of the project. + ```bash + cd Shoonya-Backend + pip install -r ./backend/deploy/requirements.txt + ``` - 3. To set up the environment variables needed for the project, run the following lines: + 3. To set up the environment variables needed for the project, run the following lines: ```bash cp .env.example ./backend/.env @@ -369,11 +306,8 @@ Method 3 : Traditional deployment using Django manage.py runserver default scrip ```bash python ./backend/manage.py runserver ``` -
- - -#### Google Cloud Logging (Optional) +### Google Cloud Logging (Optional) If Google Cloud Logging is being used, please follow these additional steps: