From 6fc6a49a26c2b75e2ea5f35b98efa14569513356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=CC=81scar=20M=2E=20Lage?= Date: Thu, 14 Nov 2024 12:12:53 +0100 Subject: [PATCH] Initial commit Docker + Django fresh --- .gitignore | 2 + Dockerfile | 25 ++ Makefile | 273 ++++++++++++++++++++++ README.md | 10 + docker-compose.yml | 36 +++ docker/entrypoint.sh | 32 +++ env.sample | 15 ++ src/blueskydj/blueskydj/__init__.py | 0 src/blueskydj/blueskydj/asgi.py | 16 ++ src/blueskydj/blueskydj/settings.py | 123 ++++++++++ src/blueskydj/blueskydj/urls.py | 22 ++ src/blueskydj/blueskydj/wsgi.py | 16 ++ src/blueskydj/core/__init__.py | 0 src/blueskydj/core/admin.py | 3 + src/blueskydj/core/apps.py | 6 + src/blueskydj/core/migrations/__init__.py | 0 src/blueskydj/core/models.py | 3 + src/blueskydj/core/tests.py | 3 + src/blueskydj/core/views.py | 3 + src/blueskydj/manage.py | 22 ++ src/requirements.txt | 3 + 21 files changed, 613 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100755 docker/entrypoint.sh create mode 100644 env.sample create mode 100644 src/blueskydj/blueskydj/__init__.py create mode 100644 src/blueskydj/blueskydj/asgi.py create mode 100644 src/blueskydj/blueskydj/settings.py create mode 100644 src/blueskydj/blueskydj/urls.py create mode 100644 src/blueskydj/blueskydj/wsgi.py create mode 100644 src/blueskydj/core/__init__.py create mode 100644 src/blueskydj/core/admin.py create mode 100644 src/blueskydj/core/apps.py create mode 100644 src/blueskydj/core/migrations/__init__.py create mode 100644 src/blueskydj/core/models.py create mode 100644 src/blueskydj/core/tests.py create mode 100644 src/blueskydj/core/views.py create mode 100755 src/blueskydj/manage.py create mode 100644 src/requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e61a70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +data/* +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..808f5db --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.11 as base + +WORKDIR /code + +# Install base system dependences +RUN apt-get -qq update && \ + apt-get -qq -y --no-install-recommends install curl net-tools \ + netcat-traditional automake autoconf sudo watch gettext locales \ + apt-transport-https ca-certificates libsasl2-dev \ + python3-dev python3-pydot && \ + rm -rf /var/lib/apt/lists/* && \ + echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ + echo "es_ES.UTF-8 UTF-8" >> /etc/locale.gen && \ + ln -fs /usr/share/zoneinfo/CET /etc/localtime + +# Install code dependencies +COPY src/requirements.txt /code/requirements.txt +RUN pip install --upgrade pip +RUN pip install -r requirements.txt + +VOLUME /code + +COPY docker/entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] +CMD ["bash"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2af93cd --- /dev/null +++ b/Makefile @@ -0,0 +1,273 @@ +## +# Makefile to help manage docker-compose services +# + + # Include environment files + include .env + export + +# Variables +THIS_FILE := $(lastword $(MAKEFILE_LIST)) +DOCKER := $(shell which docker) +DOCKER_COMPOSE := $(shell which docker-compose) + +IMAGE_DEFAULT := bskydj-app +CONTAINER_DEFAULT := bskydj-app-1 + +BACKUP_SERVICE := backup +RESTORE_SERVICE := restore + +SHELL_CMD := /bin/bash + +# Docker compose files +DOCKER_COMPOSE_FILES := -f docker-compose.yml +ifeq ($(DOCKER_ENV),local) + DOCKER_COMPOSE_FILES := -f docker-compose.yml +endif +ifeq ($(DOCKER_ENV),production) + DOCKER_COMPOSE_FILES := -f docker-compose.yml +endif + +# Services +SERVICES_DEFAULT := app db +ifeq ($(DOCKER_ENV),local) + SERVICES_DEFAULT := app db +endif +ifeq ($(DOCKER_ENV),production) + SERVICES_DEFAULT := app db +endif +SERVICE_DEFAULT := app + +container ?= $(CONTAINER_DEFAULT) +image ?= $(IMAGE_DEFAULT) +service ?= +services ?= $(SERVICES_DEFAULT) + +.DEFAULT_GOAL := help + + +## +# help +# +help: +ifeq ($(CONTAINER_DEFAULT),) + $(warning WARNING: CONTAINER_DEFAULT is not set. Please edit makefile) +endif + @echo + @echo "Make targets:" + @echo + @cat $(THIS_FILE) | \ + sed -n -E 's/^([^.][^: ]+)\s*:(([^=#]*##\s*(.*[^[:space:]])\s*)|[^=].*)$$/ \1 \4/p' | \ + sort -u | \ + expand -t15 + @echo + @echo + @echo "Target arguments:" + @echo + @echo " " "service" "\t" "Target service for docker-compose actions (default=all-services)" + @echo " " " " "\t" " - make start" + @echo " " " " "\t" " - make start service=app" + @echo " " "services" "\t" "Target services for docker-compose actions (default=all-services, space separated)" + @echo " " " " "\t" " - make stop services='app db'" + @echo " " "container""\t" "Target container for docker actions (default='$(CONTAINER_DEFAULT)')" + @echo " " " " "\t" " - make bash container=$(container)" + @echo " " "image" "\t" "Target image for interactive shell (default='$(IMAGE_DEFAULT)')" + @echo " " " " "\t" " - make it image=$(image)" + + +## +# services +# +services: ## Lists services + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) ps --services + + +## +# start +# +all: dev ## See 'dev' +start: dev ## See 'dev' +dev: ## Start containers for development [service|services] + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) up -d $(services) + $(MAKE) logs + + +## +# stop +# +stop: ## Stop containers [service|services] + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) stop $(services) + + +## +# restart +# +restart: ## Restart containers [service|services] + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) restart $(services) + + +## +# down +# +down: ## Removes containers (preserves images and volumes) + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) down + + +## +# build +# +build: ## Builds service images [service|services] + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) build $(services) + + +## +# rebuild +# +rebuild: ## Build containers without cache [service|services] + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) build --no-cache $(services) + + +## +# ps +# +status: ps ## See 'ps' +ps: ## Show status of containers [service|services] + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) ps $(services) + + +## +# interact +# +interact: it ## See `it' +it: ## Run a new container in interactive mode. Needs image name [image] +ifeq ($(image),) + $(error ERROR: 'image' is not set. Please provide 'image=' argument or edit makefile and set CONTAINER_DEFAULT) +endif + @echo + @echo "Starting interactive shell ($(SHELL_CMD)) in image container '$(image)'" + @$(DOCKER) run -it --entrypoint "$(SHELL_CMD)" $(image) + + +## +# bash +# +sh: bash ## See 'bash' +shell: bash ## See 'bash' +bash: ## Brings up a shell in default (or specified) container [container] +ifeq ($(container),) + $(error ERROR: 'container' is not set. Please provide 'container=' argument or edit makefile and set CONTAINER_DEFAULT) +endif + @echo + @echo "Starting shell ($(SHELL_CMD)) in container '$(container)'" + @$(DOCKER) exec -it "$(container)" "$(SHELL_CMD)" + + +## +# attach +# +at: attach ## See 'attach' +attach: ## Attach to a running container [container] +ifeq ($(container),) + $(error ERROR: 'container' is not set. Please provide 'container=' argument or edit makefile and set CONTAINER_DEFAULT) +endif + @echo + @echo "Attaching to '$(container)'" + @$(DOCKER) attach $(container) + + +## +# log +# +log: ## Shows log from a specific container (in 'follow' mode) [container] +ifeq ($(container),) + $(error ERROR: 'container' is not set. Please provide 'container=' argument or edit makefile and set CONTAINER_DEFAULT) +endif + @echo + @echo "Log in $(container)" + @$(DOCKER) logs -f $(container) + + +## +# logs +# +logs: ## Shows output of running containers (in 'follow' mode) [service|services] + @echo + @echo "Logs in $(services)" + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) logs -f $(services) + + +## +# rmimages +# +rmimages: ## Remove images + @echo + @echo "Remove local images" + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) down --rmi local + + +## +# clean +# +clean: ## Remove containers, images and volumes + @echo + @echo "Remove containers, images and volumes" + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) down --volumes --rmi all + + +## +# backup +# +backup: ## Run 'backup' service + @echo + @echo "Running '$(BACKUP_SERVICE)'" + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) run --rm $(BACKUP_SERVICE) + + +## +# restore +# +restore: ## Run 'restore' service + @echo + @echo "Running '$(BACKUP_SERVICE)'" + @$(DOCKER_COMPOSE) $(DOCKER_COMPOSE_FILES) run --rm $(RESTORE_SERVICE) + + +cleandb: + @prefix="bsky.local-"; \ + path="_data/backup/*" ; \ + suffix=".sql.gz"; \ + days_to_keep=10; \ + current_month=$$(date "+%m"); \ + current_day=$$(date "+%d"); \ + for file in $$path; do \ + filename=$$(basename "$$file") ; \ + if [ "$$filename" != "latest$$suffix" ]; then \ + file_year=$$(echo "$$filename" | cut -d"-" -f2) ; \ + file_month=$$(echo "$$filename" | cut -d"-" -f3) ; \ + file_day=$$(echo "$$filename" | cut -d"-" -f4 | cut -d"." -f1) ; \ + if [ "$$file_month" != "$$current_month" ]; then \ + last_day=$$(date -d "$$file_year/$$file_month/01 +1 month -1 day" +"%Y-%m-%d") ; \ + last_file=$$(echo "$$prefix$$last_day$$suffix") ; \ + if [ "$$filename" != "$$last_file" ]; then \ + echo "Cleaning: $$file" ; \ + rm $$file ; \ + else \ + echo "Storing: $$file" ; \ + fi ; \ + else \ + day_diff=$$(( $$days_to_keep - $$current_day + $$file_day )) ; \ + if [ $$day_diff -lt 0 ]; then \ + echo "Cleaning: $$file" ; \ + rm $$file ; \ + else \ + echo "Storing: $$file" ; \ + fi ; \ + fi ; \ + fi ; \ + done ; + +%: + @: + + +.PHONY: % diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f60651 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Bluesky Django + +Bluesky Django PoC + +### Install + + +### Post install + +* `make migrate` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8602182 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +name: bskydj + +services: + app: + build: . + stdin_open: true + tty: true + command: ["run-server"] + env_file: + - ./.env + depends_on: + - db + volumes: + - ./src:/code/ + ports: + - ${APP_PORT}:${APP_PORT} + networks: + - app + - default + + db: + image: mysql:9.1.0 + env_file: + - ./.env + volumes: + - ./data/mysql:/var/lib/mysql + ports: + - ${MYSQL_PORT}:3306 + networks: + - app + +networks: + app: + name: app + external: true + diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..6479d81 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +function wait_service { + while ! nc -z $1 $2; do + sleep 1 + done +} + +case $1 in + run-migrations) + echo "--> Applying migrations" + wait_service $MYSQL_HOST $MYSQL_PORT + cd $DOCUMENTROOT + exec python manage.py migrate --noinput + ;; + run-server) + echo "--> Starting Django's server" + wait_service $MYSQL_HOST $MYSQL_PORT + exec python manage.py runserver 0.0.0.0:$APP_PORT + ;; + run-tests) + echo "--> Starting Django's test framework" + wait_service $MYSQL_HOST $MYSQL_PORT + cd $DOCUMENTROOT + exec python manage.py test + ;; + *) + exec "$@" + ;; +esac diff --git a/env.sample b/env.sample new file mode 100644 index 0000000..098ab25 --- /dev/null +++ b/env.sample @@ -0,0 +1,15 @@ +PROJECT=bsky.local +MYSQL_PORT=32011 +APP_PORT=8111 + +DOCUMENTROOT=/code + +MYSQL_HOST=mysql +MYSQL_PORT=3306 +MYSQL_DATABASE=${PROJECT} +MYSQL_USER=root +MYSQL_PASSWORD=pass +MYSQL_ROOT_PASSWORD=pass + +DEBUG=True +DJANGO_SETTINGS_MODULE=project.settings.devel diff --git a/src/blueskydj/blueskydj/__init__.py b/src/blueskydj/blueskydj/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blueskydj/blueskydj/asgi.py b/src/blueskydj/blueskydj/asgi.py new file mode 100644 index 0000000..8e27f47 --- /dev/null +++ b/src/blueskydj/blueskydj/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for blueskydj project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blueskydj.settings') + +application = get_asgi_application() diff --git a/src/blueskydj/blueskydj/settings.py b/src/blueskydj/blueskydj/settings.py new file mode 100644 index 0000000..9409775 --- /dev/null +++ b/src/blueskydj/blueskydj/settings.py @@ -0,0 +1,123 @@ +""" +Django settings for blueskydj project. + +Generated by 'django-admin startproject' using Django 5.1.3. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +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/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-h#0))ubik6g6_azpi-0tf)=y%@m&488#2io&(5x+k^)10ns_41' + +# 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', +] + +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 = 'blueskydj.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + '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', + ], + }, + }, +] + +WSGI_APPLICATION = 'blueskydj.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/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/5.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/src/blueskydj/blueskydj/urls.py b/src/blueskydj/blueskydj/urls.py new file mode 100644 index 0000000..80e3ecc --- /dev/null +++ b/src/blueskydj/blueskydj/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for blueskydj project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/src/blueskydj/blueskydj/wsgi.py b/src/blueskydj/blueskydj/wsgi.py new file mode 100644 index 0000000..9b141dd --- /dev/null +++ b/src/blueskydj/blueskydj/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for blueskydj project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blueskydj.settings') + +application = get_wsgi_application() diff --git a/src/blueskydj/core/__init__.py b/src/blueskydj/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blueskydj/core/admin.py b/src/blueskydj/core/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/blueskydj/core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/blueskydj/core/apps.py b/src/blueskydj/core/apps.py new file mode 100644 index 0000000..8115ae6 --- /dev/null +++ b/src/blueskydj/core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core' diff --git a/src/blueskydj/core/migrations/__init__.py b/src/blueskydj/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/blueskydj/core/models.py b/src/blueskydj/core/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/src/blueskydj/core/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/src/blueskydj/core/tests.py b/src/blueskydj/core/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/blueskydj/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/blueskydj/core/views.py b/src/blueskydj/core/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/blueskydj/core/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/blueskydj/manage.py b/src/blueskydj/manage.py new file mode 100755 index 0000000..05d5fb5 --- /dev/null +++ b/src/blueskydj/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blueskydj.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..e2ae4cc --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,3 @@ +asgiref==3.8.1 +Django==5.1.3 +sqlparse==0.5.2