diff --git a/.env b/.env deleted file mode 100644 index 28e29d9..0000000 --- a/.env +++ /dev/null @@ -1,32 +0,0 @@ -# Django settings -SECRET_KEY="FollowTheWhiteRabbit" -DEBUG=True -DJANGO_ALLOWED_HOSTS=['*'] - -# PostgreSQL settings -POSTGRES_DB=players_db -POSTGRES_USER=42student -POSTGRES_PASSWORD=qwerty - -# Django settings -DB_HOST=db -DB_PORT=5432 -PWD_PATH=${PWD} -PROJECT_PATH=${PWD_PATH}/pong -DJANGO_LOGS=${PWD_PATH}/logs - -# ElasticSearch settings -STACK_VERSION=8.14.3 -CLUSTER_NAME=docker-cluster -LICENSE=basic - -ELASTIC_USERNAME=elastic -ELASTIC_PASSWORD=elastic_pass - -# Kibana settings -KIBANA_PORT=5601 -KIBANA_USERNAME=kibana_system -KIBANA_PASSWORD=kibana_pass - -ENCRYPTION_KEY=c34d38b3a14956121ff2170e5030b471551370178f43e5626eec58b04a30fae2 - diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..062be5a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,236 @@ +services: + setup: + image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} + container_name: setup + user: "0" + volumes: + - certs:/usr/share/elasticsearch/config/certs + command: > + bash -c ' + if [ x${ELASTIC_PASSWORD} == x ]; then + echo "Set the ELASTIC_PASSWORD environment variable in the .env file"; + exit 1; + elif [ x${KIBANA_PASSWORD} == x ]; then + echo "Set the KIBANA_PASSWORD environment variable in the .env file"; + exit 1; + fi; + if [ ! -f config/certs/ca.zip ]; then + echo "Creating CA"; + bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip; + unzip config/certs/ca.zip -d config/certs; + fi; + if [ ! -f config/certs/certs.zip ]; then + echo "Creating certs"; + echo -ne \ + "instances:\n"\ + " - name: es01\n"\ + " dns:\n"\ + " - es01\n"\ + " - localhost\n"\ + " ip:\n"\ + " - 127.0.0.1\n"\ + " - name: kibana\n"\ + " dns:\n"\ + " - kibana\n"\ + " - localhost\n"\ + " ip:\n"\ + " - 127.0.0.1\n"\ + > config/certs/instances.yml; + + bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key; + unzip config/certs/certs.zip -d config/certs; + fi; + + echo "Setting file permissions" + chown -R root:root config/certs; + find . -type d -exec chmod 750 \{\} \;; + find . -type f -exec chmod 640 \{\} \;; + + echo "Waiting for Elasticsearch availability"; + until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q "missing authentication credentials"; do sleep 30; done; + echo "Setting kibana_system password"; + until curl -s -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" https://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done; + echo "All done!"; + ' + healthcheck: + test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"] + interval: 1s + timeout: 5s + retries: 120 + + backend: + build: + context: . + dockerfile: Dockerfile + image: backend + container_name: backend + restart: always + command: /bin/sh -c "sleep 5 && + venv/bin/python manage.py makemigrations --noinput && + venv/bin/python manage.py migrate --noinput && + venv/bin/python manage.py collectstatic --noinput && + venv/bin/daphne -b 0.0.0.0 -p 8080 pong.asgi:application" + volumes: + - pong:/transcendence/pong + - pong_django_logs:/transcendence/logs + ports: + - 8080:8080 + networks: + - app-network + environment: + DB_HOST: db + DB_PORT: 5432 + DB_NAME: ${POSTGRES_DB} + DB_USER: ${POSTGRES_USER} + DB_PASSWORD: ${POSTGRES_PASSWORD} + depends_on: + - db + healthcheck: + test: ["CMD-SHELL", "curl", "http://localhost:8080"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + db: + image: postgres:latest + container_name: postgres + restart: always + volumes: + - pong_pg_data:/var/lib/postgresql/data + ports: + - "5432:5432" + networks: + - app-network + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + + es01: + image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} + container_name: es01 + depends_on: + setup: + condition: service_healthy + volumes: + - certs:/usr/share/elasticsearch/config/certs:ro + - pong_es_data_01:/usr/share/elasticsearch/data + labels: + co.elastic.logs/module: elasticsearch + ports: + - 9200:9200 + environment: + - node.name=es01 + - cluster.name=${CLUSTER_NAME} + - discovery.type=single-node + - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} + - bootstrap.memory_lock=true + - xpack.security.enabled=true + - xpack.security.http.ssl.enabled=true + - xpack.security.http.ssl.key=certs/es01/es01.key + - xpack.security.http.ssl.certificate=certs/es01/es01.crt + - xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt + - xpack.security.transport.ssl.enabled=true + - xpack.security.transport.ssl.key=certs/es01/es01.key + - xpack.security.transport.ssl.certificate=certs/es01/es01.crt + - xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt + - xpack.security.transport.ssl.verification_mode=certificate + - xpack.license.self_generated.type=${LICENSE} + healthcheck: + test: + [ + "CMD-SHELL", + "curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'", + ] + interval: 10s + timeout: 10s + retries: 120 + + kibana: + image: docker.elastic.co/kibana/kibana:${STACK_VERSION} + container_name: kibana + labels: + co.elastic.logs/module: kibana + depends_on: + es01: + condition: service_healthy + volumes: + - certs:/usr/share/kibana/config/certs:ro + - pong_kibana:/usr/share/kibana/data + ports: + - 5601:5601 + environment: + - SERVERNAME=kibana + - ELASTICSEARCH_HOSTS=https://es01:9200 + - ELASTICSEARCH_USERNAME=${KIBANA_USERNAME} + - ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD} + - ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt + - XPACK_SECURITY_ENCRYPTIONKEY=${ENCRYPTION_KEY} + - XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${ENCRYPTION_KEY} + - XPACK_REPORTING_ENCRYPTIONKEY=${ENCRYPTION_KEY} + healthcheck: + test: + [ + "CMD-SHELL", + "curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'" + ] + interval: 10s + timeout: 10s + retries: 120 + + logstash01: + image: docker.elastic.co/logstash/logstash:${STACK_VERSION} + container_name: logstash01 + labels: + co.elastic.logs/module: logstash + user: root + depends_on: + es01: + condition: service_healthy + kibana: + condition: service_healthy + volumes: + - certs:/usr/share/logstash/certs + - pong_logstash_data01:/usr/share/logstash/data + - ./config/logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro + - pong_django_logs:/usr/share/logstash/logs + ports: + - "5044:5044/udp" + command: logstash -f /usr/share/logstash/pipeline/logstash.conf + environment: + - NODE_NAME="logstash" + - ELASTIC_HOSTS=https://es01:9200 + - ELASTIC_USER=${ELASTIC_USERNAME} + - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} + - xpack.monitoring.enabled=false + +volumes: + pong: + driver: local + driver_opts: + type: none + device: ${PROJECT_PATH} + o: bind + pong_django_logs: + driver: local + pong_pg_data: + driver: local + pong_es_data_01: + driver: local + pong_kibana: + driver: local + pong_logstash_data01: + driver: local + certs: + driver: local + +networks: + app-network: + name: app-network + driver: bridge diff --git a/env_template b/env_template index b297aeb..92f05fd 100644 --- a/env_template +++ b/env_template @@ -6,7 +6,7 @@ DJANGO_ALLOWED_HOSTS=['*'] # PostgreSQL settings POSTGRES_DB=players_db POSTGRES_USER=42student -POSTGRES_PASSWORD= +POSTGRES_PASSWORD=postgre_pass # Django settings DB_HOST=db @@ -20,11 +20,11 @@ CLUSTER_NAME=docker-cluster LICENSE=basic ELASTIC_USERNAME=elastic -ELASTIC_PASSWORD= +ELASTIC_PASSWORD=elastic_pass # Kibana settings KIBANA_PORT=5601 -KIBANA_USERNAME= -KIBANA_PASSWORD= +KIBANA_USERNAME=kibana_system +KIBANA_PASSWORD=kibana_pass ENCRYPTION_KEY=c34d38b3a14956121ff2170e5030b471551370178f43e5626eec58b04a30fae2 diff --git a/logs/django.log b/logs/django.log deleted file mode 100644 index 7683829..0000000 --- a/logs/django.log +++ /dev/null @@ -1,10 +0,0 @@ -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} -{"message": "Not Found: /favicon.ico", "taskName": null, "status_code": 404, "request": ""} diff --git a/makefile b/makefile index 45a9a55..e7fbc31 100644 --- a/makefile +++ b/makefile @@ -4,7 +4,7 @@ CONTAINER=$(c) up: down $(COMPOSE) build - $(COMPOSE) up --remove-orphans $(CONTAINER) + $(COMPOSE) up -d $(CONTAINER) || true build: $(COMPOSE) build $(CONTAINER) @@ -20,8 +20,13 @@ down: destroy: $(COMPOSE) down -v --rmi all + +kill-pid: sudo lsof -i :5432 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true - sudo lsof -i :80 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :5601 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :9200 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :8080 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true + sudo lsof -i :5044 | awk 'NR>1 {print $$2}' | xargs sudo kill -9 || true logs: $(COMPOSE) logs -f $(CONTAINER) diff --git a/pong/game/game.py b/pong/game/game.py index bfc5530..c9b4da5 100644 --- a/pong/game/game.py +++ b/pong/game/game.py @@ -241,7 +241,7 @@ class Game: if not self.localgame: await self.player2.send(end_message) if hasattr(self, 'tournament'): - await sync_to_async(handle_game_data)(self.game_state['player1_name'], self.game_state['player2_name'], + await sync_to_async(handle_game_data)(self.game_state['player1_name'], self.game_state['player2_name'], self.game_state['player1_score'], self.game_state['player2_score'], self.bt1, self.bt2, duration, True, self.tournament.tournoi_reg) else: diff --git a/pong/settings.py b/pong/settings.py new file mode 100644 index 0000000..b44ccdf --- /dev/null +++ b/pong/settings.py @@ -0,0 +1,172 @@ +# /pong/settings.py + +""" +Django settings for pong project. + +Generated by 'django-admin startproject' using Django 3.2. +""" + +import os +import logging.config +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '12345678' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'channels', + 'pong.game', + 'rest_framework' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'pong.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'pong', 'static')], # Ensure templates are found + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +ASGI_APPLICATION = 'pong.asgi.application' + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('DB_NAME'), + 'USER': os.getenv('DB_USER'), + 'PASSWORD': os.getenv('DB_PASSWORD'), + 'HOST': os.getenv('DB_HOST'), + 'PORT': '5432', + } +} + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' +STATICFILES_DIRS = [os.path.join(BASE_DIR, 'pong/static')] +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Channels +# Define the channel layers for WebSockets +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels.layers.InMemoryChannelLayer', + }, +} + +LOGGING = { + 'version': 1, # The version of the logging configuration schema + 'disable_existing_loggers': False, # Allows existing loggers to keep logging + 'formatters': { # Defines how log messages will be formatted + 'json': { + '()': 'pythonjsonlogger.jsonlogger.JsonFormatter', + # Formatter that outputs logs in JSON format, which is ideal for ingestion by Logstash. + }, + 'default': { + 'format': '[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s', + # This is a basic text formatter with timestamp, log level, logger name, line number, and the actual message. + }, + }, + 'handlers': { # Handlers determine where the log messages are sent + 'file': { + 'level': 'INFO', # Minimum log level to be handled (INFO and above) + 'class': 'logging.FileHandler', + 'filename': os.path.join(BASE_DIR, 'logs/django.log'), # The file where logs will be saved + 'formatter': 'json', # Uses the JSON formatter defined above + }, + 'console': { + 'level': 'DEBUG', # Minimum log level to be handled (DEBUG and above) + 'class': 'logging.StreamHandler', + 'formatter': 'default', # Uses the default text formatter + }, + }, + 'loggers': { # Loggers are the actual log streams that get configured + 'django': { # The Django logger catches all messages sent by the Django framework + 'handlers': ['file', 'console'], # Sends logs to both the file and the console + 'level': 'DEBUG', # Minimum log level to be logged + 'propagate': True, # If True, messages will be passed to the parent loggers as well + }, + }, +}