diff --git a/.docker/bot/Dockerfile b/.docker/bot/Dockerfile new file mode 100644 index 00000000..01dcc1ef --- /dev/null +++ b/.docker/bot/Dockerfile @@ -0,0 +1,28 @@ +FROM python:3.11-slim-bullseye AS base + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +FROM base AS builder + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +FROM base AS development + +COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages + +CMD ["python3", "main.py"] + +FROM base AS production + +COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages + +COPY . . + +CMD ["python3", "main.py"] diff --git a/.docker/nginx/Dockerfile b/.docker/nginx/Dockerfile new file mode 100755 index 00000000..5d969fd7 --- /dev/null +++ b/.docker/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:1.26-alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf \ No newline at end of file diff --git a/.docker/nginx/nginx.conf b/.docker/nginx/nginx.conf new file mode 100755 index 00000000..da7697e5 --- /dev/null +++ b/.docker/nginx/nginx.conf @@ -0,0 +1,20 @@ +server { + listen 80; + server_name localhost; + # listen 443 ssl http2; + # server_name localhost; + # ssl_protocols TLSv1.2 TLSv1.3; + # ssl_prefer_server_ciphers on; + # ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + + location / { + proxy_pass http://web:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; +} \ No newline at end of file diff --git a/.docker/web/Dockerfile b/.docker/web/Dockerfile new file mode 100755 index 00000000..b7c444c7 --- /dev/null +++ b/.docker/web/Dockerfile @@ -0,0 +1,36 @@ +FROM python:3.11-slim-bullseye AS base + +WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +FROM base AS builder +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +FROM base AS development +WORKDIR /app +COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages +ENV FLASK_APP=app.py \ + FLASK_ENV=development \ + FLASK_RUN_PORT=5000 \ + FLASK_RUN_HOST=0.0.0.0 +EXPOSE 5000 +CMD ["flask", "run"] + +FROM base AS gunicorn +RUN pip install --no-cache-dir gunicorn + +FROM base AS production + +COPY --from=builder /usr/local/bin /usr/local/bin +COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages +COPY --from=gunicorn /usr/local/bin /usr/local/bin +COPY --from=gunicorn /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages + +COPY . . +ENV GUNICORN_CMD_ARGS="--bind=0.0.0.0:5000 --workers=3" +EXPOSE 5000 +CMD ["gunicorn", "app:app"] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..17aee10c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,179 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +# build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Docker +.docker/ +**/docker-compose.yml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.*.yaml +**/compose.yml +**/compose.*.yml +**/compose.yaml +**/compose.*.yaml + +db-data/ +log/ + +# CI/CD +.github \ No newline at end of file diff --git a/.env.example b/.env.example index 0cff0b3f..41e42dd5 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,14 @@ MYSQL_PORT=資料庫連接埠 MYSQL_DATABASE=資料庫名稱 HOST=資料庫主機位址 +# Docker MySQL initialization +MYSQL_ROOT_PASSWORD=資料庫root密碼(Docker用戶必填) +MYSQL_HOST_PORT=本地訪問容器內MySQL的端口號 +NGINX_HOST_PORT=本地訪問容器內NGINX的端口號 +NGINX_HTTPS_HOST_PORT=本地訪問容器內NGINX用於HTTPS的端口號 +FLASK_HOST_PORT=本地訪問容器內商店的端口號 +NGINX_LOG_PATH=本地 NGINX Log 儲存位置 + # Global configuration DISCORD_TOKEN=Discord機器人token GUILD_ID=機器人所在伺服器ID diff --git a/.gitignore b/.gitignore index 3d17a4b5..5f860c93 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ read.txt # Docker docker-compose.override.yml +log/ \ No newline at end of file diff --git a/docker-compose.override.yaml b/docker-compose.override.yaml new file mode 100644 index 00000000..1d97fae0 --- /dev/null +++ b/docker-compose.override.yaml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + web: + build: + target: development + volumes: + - .:/app + bot: + build: + target: development + volumes: + - .:/app \ No newline at end of file diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml new file mode 100644 index 00000000..e92baf88 --- /dev/null +++ b/docker-compose.prod.yaml @@ -0,0 +1,9 @@ +version: '3.8' + +services: + web: + build: + target: production + bot: + build: + target: production \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..82398a0a --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,71 @@ +version: '3.8' + +services: + db: + image: mysql:8.0 + restart: unless-stopped + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "${MYSQL_USER}", "--password=${MYSQL_PASSWORD}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 600s + volumes: + - db-data:/var/lib/mysql + - ./static:/docker-entrypoint-initdb.d + networks: + - backnet + env_file: + - .env + expose: + - 3306 + + web: + build: + context: . + dockerfile: .docker/web/Dockerfile + restart: unless-stopped + ports: + - ${FLASK_HOST_PORT}:5000 + networks: + - backnet + - frontnet + depends_on: + db: + condition: service_healthy + env_file: + - .env + + bot: + build: + context: . + dockerfile: .docker/bot/Dockerfile + restart: unless-stopped + networks: + - backnet + depends_on: + db: + condition: service_healthy + env_file: + - .env + + nginx: + build: + context: .docker/nginx + restart: unless-stopped + ports: + - ${NGINX_HOST_PORT}:80 + # - ${NGINX_HTTPS_HOST_PORT}:443 + depends_on: + - web + volumes: + - "${NGINX_LOG_PATH}:/var/log/nginx" + networks: + - frontnet + +volumes: + db-data: + +networks: + backnet: + frontnet: \ No newline at end of file diff --git a/run-dev.sh b/run-dev.sh new file mode 100644 index 00000000..20da5cbd --- /dev/null +++ b/run-dev.sh @@ -0,0 +1 @@ +docker compose up --build diff --git a/run-prod.sh b/run-prod.sh new file mode 100644 index 00000000..14996e9f --- /dev/null +++ b/run-prod.sh @@ -0,0 +1 @@ +docker compose -f docker-compose.yaml -f docker-compose.prod.yaml up --build