Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Ignore VCS
.git
.gitignore

# Python artifacts
__pycache__/
*.py[cod]
*.pyc
*.pyo
*.pyd
*.so

# Local env & tooling
.env
.env.*
venv/
.virtualenv/
.cache/
.mypy_cache/
.pytest_cache/
.coverage

# Django/SQLite development DB (use real DB in production)
db.sqlite3

# Node / misc
node_modules/

# Ansible deployment artifacts (not needed in runtime image)
ansible/

# Other
*.log
14 changes: 13 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
DJANGO_DEBUG=True
DJANGO_SECRET_KEY=change-me
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The default value for DJANGO_DEBUG is inconsistent between files. In .env.example it's false (string), but django-environ expects a boolean. While django-environ can parse string values like "false", "False", "0", etc., using "true" or "false" strings is clearer. However, in config/settings.py line 23, env.bool() is used which properly handles this. Consider documenting that this should be "true" or "false" (lowercase) for consistency.

Suggested change
DJANGO_SECRET_KEY=change-me
DJANGO_SECRET_KEY=change-me
# Set DJANGO_DEBUG to "true" or "false" (lowercase) for clarity.

Copilot uses AI. Check for mistakes.
DJANGO_DEBUG=false
DJANGO_SETTINGS_MODULE=config.settings
DJANGO_STATIC_ROOT=/app/static/
PORT=8000
GUNICORN_WORKERS=3
IMAGE_NAME=histrio/idontneedit:latest
SITE_DOMAIN=idontneedit.org.ru
POSTGRES_DB=idontneedit
POSTGRES_USER=idontneedit
POSTGRES_PASSWORD=changeme
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
32 changes: 30 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jobs:
- name: Install uv
run: pip install uv


- name: Install project
run: uv sync

Expand All @@ -38,11 +37,40 @@ jobs:
- name: Run tests
run: uv run python manage.py test

deploy:
build-image:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest

steps:
- name: checkout
uses: actions/checkout@v4

- name: set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: build and push Docker image
uses: docker/build-push-action@v5
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The docker/build-push-action is pinned to v5, which may not be the latest version. As of early 2025, v6 might be available with improvements and bug fixes. Consider updating to the latest version or using a more flexible version specifier like v5 or checking the current latest version.

Note: If v5 is intentionally used due to compatibility, this can be disregarded.

Suggested change
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6

Copilot uses AI. Check for mistakes.
with:
context: .
push: true
tags: |
histrio/idontneedit:latest
histrio/idontneedit:${{ github.sha }}
cache-from: type=registry,ref=histrio/idontneedit:buildcache
cache-to: type=registry,ref=histrio/idontneedit:buildcache,mode=max

deploy:
needs: build-image
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest

steps:
- name: checkout
uses: actions/checkout@v4
Expand Down
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
- id: forbid-new-submodules
- id: no-commit-to-branch
args: ['--branch', 'main', '--branch', 'master']
- id: check-case-conflict
- id: check-merge-conflict
- id: detect-private-key
- repo: https://github.com/psf/black
rev: 25.11.0
hooks:
Expand All @@ -20,3 +26,11 @@ repos:
rev: v1.4.0
hooks:
- id: detect-secrets

- repo: local
hooks:
- id: forbid-env-files
name: Forbid .env files
entry: '.env files must not be committed'
language: fail
files: '(^|/)\.env($|\.(?!example$).*)'
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The regular expression pattern '(^|/)\.env($|\.(?!example$).*)' may not correctly match all intended .env files. The negative lookahead (?!example$) only checks that the string after the dot is not exactly "example", but the pattern requires additional characters after due to .*. This means .env.local would match, but a file named .env. (with trailing dot) might behave unexpectedly.

Consider simplifying to: '(^|/)\.env($|\.(?!example))' or more explicitly: '^\.env$|^\.env\..+$' while excluding .env.example explicitly.

Suggested change
files: '(^|/)\.env($|\.(?!example$).*)'
files: '^\.env$|^\.env\.(?!example$).+'

Copilot uses AI. Check for mistakes.
48 changes: 48 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
ARG PYTHON_VERSION=3.13
FROM python:${PYTHON_VERSION}-slim AS base

ENV DEBIAN_FRONTEND=noninteractive \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*

RUN useradd --uid 10001 --create-home --shell /usr/sbin/nologin app \
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Hardcoded UID 10001 may conflict with existing UIDs on the host system when mounting volumes. Consider making the UID configurable via build argument or document why this specific UID was chosen.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The user is created with shell /usr/sbin/nologin, which is appropriate for security. However, this will prevent interactive debugging if needed. Consider documenting this choice or using /bin/false which is more commonly used for this purpose.

Suggested change
RUN useradd --uid 10001 --create-home --shell /usr/sbin/nologin app \
RUN useradd --uid 10001 --create-home --shell /bin/false app \

Copilot uses AI. Check for mistakes.
&& mkdir -p /app /app/static \
&& chown -R app:app /app

WORKDIR /app

FROM base AS runtime

COPY requirements.txt ./
RUN pip install --upgrade pip wheel \
&& pip install -r requirements.txt --no-cache-dir

ENV DJANGO_SETTINGS_MODULE=config.settings \
DJANGO_STATIC_ROOT=/app/static/ \
GUNICORN_WORKERS=3 \
GUNICORN_BIND=0.0.0.0:8000 \
GUNICORN_MAX_REQUESTS=1000 \
GUNICORN_MAX_REQUESTS_JITTER=100 \
PORT=8000
Comment on lines +31 to +34
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment variables GUNICORN_BIND and GUNICORN_MAX_REQUESTS are defined but never used in the CMD directive. The CMD at line 48 only uses PORT and GUNICORN_WORKERS. Either use these variables in the command or remove them:

CMD ["/bin/sh", "-c", "exec gunicorn config.wsgi:application --bind ${GUNICORN_BIND:-0.0.0.0:8000} --workers ${GUNICORN_WORKERS:-3} --max-requests ${GUNICORN_MAX_REQUESTS:-1000} --max-requests-jitter ${GUNICORN_MAX_REQUESTS_JITTER:-100} --access-logfile - --error-logfile -"]

Copilot uses AI. Check for mistakes.


COPY . /app

RUN chown -R app:app /app
Comment on lines +37 to +39
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The COPY . /app command at line 37 happens after pip install, which is good for cache efficiency. However, copying everything and then changing ownership with chown -R creates an additional layer. Consider combining these operations or copying with the correct ownership:

COPY --chown=app:app . /app

This reduces the image size by avoiding a duplicate layer.

Suggested change
COPY . /app
RUN chown -R app:app /app
COPY --chown=app:app . /app

Copilot uses AI. Check for mistakes.

USER app

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS "http://127.0.0.1:${PORT}/" || exit 1

CMD ["/bin/sh", "-c", "exec gunicorn config.wsgi:application --bind 0.0.0.0:${PORT:-8000} --workers ${GUNICORN_WORKERS:-3} --access-logfile - --error-logfile -"]
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The CMD uses shell expansion for environment variables which is correct, but this could be simplified using the default environment values already set on lines 30-31. Consider referencing $GUNICORN_BIND directly or remove the defaults here to avoid duplication with ENV declarations.

Suggested change
CMD ["/bin/sh", "-c", "exec gunicorn config.wsgi:application --bind 0.0.0.0:${PORT:-8000} --workers ${GUNICORN_WORKERS:-3} --access-logfile - --error-logfile -"]
CMD ["/bin/sh", "-c", "exec gunicorn config.wsgi:application --bind 0.0.0.0:$PORT --workers $GUNICORN_WORKERS --access-logfile - --error-logfile -"]

Copilot uses AI. Check for mistakes.
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ requirements.txt: uv.lock
@test -r .env \
&& echo "Your .env is older than .env.example" \
|| cp .env.example .env

DOCKER?=docker
IMAGE_REPO:=histrio/idontneedit
IMAGE_TAG?=$(shell git rev-parse --short HEAD)
LATEST_TAG?=latest

.PHONY: image.build
image.build:
@$(DOCKER) build -t $(IMAGE_REPO):$(IMAGE_TAG) -t $(IMAGE_REPO):$(LATEST_TAG) .

.PHONY: image.push
image.push:
@$(DOCKER) push $(IMAGE_REPO):$(IMAGE_TAG)
@$(DOCKER) push $(IMAGE_REPO):$(LATEST_TAG)
Comment on lines +21 to +31
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image.build and image.push targets use IMAGE_TAG?=$(shell git rev-parse --short HEAD) which will fail if the directory is not a git repository or git is not installed. Consider adding error handling or documenting that these targets require git.

Suggested change
IMAGE_TAG?=$(shell git rev-parse --short HEAD)
LATEST_TAG?=latest
.PHONY: image.build
image.build:
@$(DOCKER) build -t $(IMAGE_REPO):$(IMAGE_TAG) -t $(IMAGE_REPO):$(LATEST_TAG) .
.PHONY: image.push
image.push:
@$(DOCKER) push $(IMAGE_REPO):$(IMAGE_TAG)
@$(DOCKER) push $(IMAGE_REPO):$(LATEST_TAG)
# NOTE: The image.build and image.push targets require git to be installed and the directory to be a git repository.
IMAGE_TAG?=$(shell git rev-parse --short HEAD)
LATEST_TAG?=latest
.PHONY: image.build
image.build:
@if ! command -v git >/dev/null 2>&1; then \
echo "Error: git is not installed. Cannot determine IMAGE_TAG."; exit 1; \
fi; \
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
echo "Error: Not a git repository. Cannot determine IMAGE_TAG."; exit 1; \
fi; \
$(DOCKER) build -t $(IMAGE_REPO):$(IMAGE_TAG) -t $(IMAGE_REPO):$(LATEST_TAG) .
.PHONY: image.push
image.push:
@if ! command -v git >/dev/null 2>&1; then \
echo "Error: git is not installed. Cannot determine IMAGE_TAG."; exit 1; \
fi; \
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
echo "Error: Not a git repository. Cannot determine IMAGE_TAG."; exit 1; \
fi; \
$(DOCKER) push $(IMAGE_REPO):$(IMAGE_TAG)
$(DOCKER) push $(IMAGE_REPO):$(LATEST_TAG)

Copilot uses AI. Check for mistakes.
19 changes: 11 additions & 8 deletions ansible/Caddyfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
idontneedit.org.ru {
{$SITE_DOMAIN} {
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Caddyfile references environment variable {$SITE_DOMAIN}, but this variable is not explicitly set in docker-compose.yml. While it's defined in the env.j2 template and .env.example, the Caddy container needs this variable to be available. Ensure that docker-compose.yml passes this environment variable to the caddy service, either through env_file or explicitly in the environment section.

Copilot uses AI. Check for mistakes.
encode zstd gzip


handle_path /static/* {
root * /srv/idontneedit/static
file_server
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}

handle_path /media/* {
root * /srv/idontneedit/media
handle_path /static/* {
root * /app/static
header Cache-Control "public, max-age=31536000, immutable"
file_server
}

reverse_proxy unix//srv/idontneedit/run/gunicorn.sock
reverse_proxy app:8000
}
3 changes: 3 additions & 0 deletions ansible/ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
Loading