Skip to content

Conversation

DonnieBLT
Copy link
Collaborator

@DonnieBLT DonnieBLT commented Oct 3, 2025

Summary by CodeRabbit

  • New Features

    • Added an Ansible-based automated deployment for the Django app, provisioning app, DB, services (Uvicorn/Nginx), firewall, and optional remote PostgreSQL, with idempotent setup and a final deployment summary.
  • Documentation

    • Added a deployment guide covering inventory setup, running the playbook, static/media, system services, firewall ports, and optional HTTPS recommendations.
  • Chores

    • Expanded environment file ignore pattern and added ignores for inventory and credentials to protect sensitive data.

Copy link
Contributor

coderabbitai bot commented Oct 3, 2025

Walkthrough

Introduces an Ansible-based deployment for a Django project and updates ignore rules. Adds an Ansible playbook and README describing deployment steps, configures system packages, Python environment, database, systemd service (Uvicorn), Nginx proxy, UFW rules, and optional PostgreSQL remote access. Updates .gitignore patterns and adds specific ignored files.

Changes

Cohort / File(s) Summary of Changes
Repo housekeeping
./.gitignore
Expanded Python env ignore from *.env to *.env.*; added ansible/inventory.yml and google-credentials.json to ignore list.
Ansible deployment
ansible/README.md, ansible/playbook.yml
Added minimal Ansible deployment: docs covering inventory, playbook usage, system components (Poetry export, systemd gunicorn-blt mention, Nginx, UFW, Certbot notes); new playbook to provision packages, create app user/dirs, clone repo, set up venv/deps, write .env.production and symlink .env, run collectstatic/migrate, ensure PostgreSQL and optionally remote access, create DB/user, configure systemd service (Uvicorn), configure Nginx reverse proxy, open firewall ports, and handlers for restarts with final summary.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant A as Ansible (Controller)
  participant S as Target Server
  participant Apt as apt/packages
  participant Git as git
  participant Py as Python venv/pip
  participant DJ as Django manage.py
  participant PG as PostgreSQL
  participant Sys as systemd (Uvicorn)
  participant NX as Nginx
  participant UFW as UFW

  Dev->>A: ansible-playbook -i inventory.yml playbook.yml
  A->>S: Connect via SSH

  A->>Apt: Install system packages
  A->>S: Create app user and directories
  A->>Git: Clone/Update repo on server

  A->>Py: Create venv, upgrade pip/setuptools
  A->>Py: Install deps (poetry export or install)

  A->>S: Write .env.production and symlink .env
  A->>DJ: collectstatic, migrate (within project dir)

  A->>PG: Ensure service running
  A->>PG: Create DB user and database (idempotent)
  alt Remote PG enabled
    A->>PG: Update postgresql.conf / pg_hba.conf
    A->>PG: Restart via handlers
  end

  A->>Sys: Create/enable systemd service (Uvicorn)
  A->>NX: Configure site, enable, disable default
  A->>NX: Reload on change

  A->>UFW: Allow 22/80/443 (and 5432 if enabled)
  A->>UFW: Enable firewall

  A-->>Dev: Print deployment summary (URL, PG access)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “ansible setup” directly reflects the primary addition of Ansible configuration and playbooks in this pull request, offering a clear and concise summary of the main change without unnecessary detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ansible_setup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces Ansible automation for deploying the BLT Django application. It provides infrastructure-as-code setup for server provisioning, application deployment, and service configuration.

  • Adds complete Ansible playbook for automated Django app deployment
  • Configures systemd service with uvicorn ASGI server
  • Sets up Nginx reverse proxy with static file serving

Reviewed Changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 4 comments.

File Description
ansible/playbook.yml Main deployment playbook with tasks for system setup, Django deployment, PostgreSQL configuration, and web server setup
ansible/README.md Documentation for using the Ansible deployment system

Comment on lines +21 to +22
- Creates a systemd service `gunicorn-blt`.
- Nginx reverse proxies to Gunicorn on port 8000.
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

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

The documentation incorrectly references 'gunicorn-blt' service and Gunicorn server, but the playbook actually creates a 'blt-uvicorn' service using uvicorn ASGI server. Update documentation to match the actual implementation.

Suggested change
- Creates a systemd service `gunicorn-blt`.
- Nginx reverse proxies to Gunicorn on port 8000.
- Creates a systemd service `blt-uvicorn`.
- Nginx reverse proxies to uvicorn (ASGI) server on port 8000.

Copilot uses AI. Check for mistakes.

content: |
DEBUG=False
ALLOWED_HOSTS=.{{ domain }},{{ domain }},127.0.0.1,localhost
SECRET_KEY={{ lookup('password', '/dev/null', length=64, chars='ascii_letters,digits') }}
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

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

The SECRET_KEY is regenerated on every playbook run because it uses '/dev/null' as the password file. This will invalidate user sessions and potentially cause Django to reject existing signed data. Use a persistent file path like '/tmp/django_secret_key' or generate it once and store it securely.

Suggested change
SECRET_KEY={{ lookup('password', '/dev/null', length=64, chars='ascii_letters,digits') }}
SECRET_KEY={{ lookup('password', '{{ app_dir }}/shared/secret_key', length=64, chars='ascii_letters,digits') }}

Copilot uses AI. Check for mistakes.

Comment on lines +240 to +244
loop:
- '22'
- '80'
- '443'
- '5432'
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

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

PostgreSQL port 5432 is opened in the firewall unconditionally, but remote PostgreSQL access is controlled by the 'enable_remote_postgres' variable. The firewall rule should only open port 5432 when remote access is actually enabled to reduce attack surface.

Suggested change
loop:
- '22'
- '80'
- '443'
- '5432'
loop: "{{ ['22', '80', '443'] + (enable_remote_postgres | bool | ternary(['5432'], [])) }}"

Copilot uses AI. Check for mistakes.

Comment on lines +146 to +149
become_user: postgres
shell: |
psql -tc "SELECT 1 FROM pg_roles WHERE rolname='{{ postgres_db_user }}'" | grep -q 1 || psql -c "CREATE ROLE {{ postgres_db_user }} LOGIN PASSWORD '{{ postgres_db_password }}';"
changed_when: false
Copy link

Copilot AI Oct 3, 2025

Choose a reason for hiding this comment

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

The PostgreSQL password is exposed in the command line and process list. Use the postgresql_user Ansible module instead of shell commands to securely create database users without exposing credentials.

Suggested change
become_user: postgres
shell: |
psql -tc "SELECT 1 FROM pg_roles WHERE rolname='{{ postgres_db_user }}'" | grep -q 1 || psql -c "CREATE ROLE {{ postgres_db_user }} LOGIN PASSWORD '{{ postgres_db_password }}';"
changed_when: false
community.postgresql.postgresql_user:
name: "{{ postgres_db_user }}"
password: "{{ postgres_db_password }}"
login: true
state: present
become_user: postgres

Copilot uses AI. Check for mistakes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0e486a9 and da0017f.

📒 Files selected for processing (3)
  • .gitignore (2 hunks)
  • ansible/README.md (1 hunks)
  • ansible/playbook.yml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Tests
  • GitHub Check: docker-test

Comment on lines +11 to +12
enable_remote_postgres: "{{ enable_remote_postgres | default(true) }}"
pre_tasks:
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Lock down PostgreSQL remote access by default.

enable_remote_postgres currently defaults to true and we always open UFW port 5432 plus append a pg_hba rule for 0.0.0.0/0. On a fresh run this exposes the database listener to the entire internet with just password auth, which is a critical security regression.

Please make remote access opt‑in (default false) and gate both the pg_hba change and the firewall rule behind that flag. Example:

-    enable_remote_postgres: "{{ enable_remote_postgres | default(true) }}"
+    enable_remote_postgres: "{{ enable_remote_postgres | default(false) }}"
...
-    - name: Open firewall ports
+    - name: Open firewall ports
       ufw:
         rule: allow
         port: "{{ item }}"
       loop:
         - '22'
         - '80'
         - '443'
-        - '5432'
+    - name: Allow PostgreSQL port when remote access enabled
+      ufw:
+        rule: allow
+        port: '5432'
+      when: enable_remote_postgres | bool

That keeps the database private unless the operator explicitly opts in.

Also applies to: 157-171, 236-244

🤖 Prompt for AI Agents
In ansible/playbook.yml around lines 11-12 (and similarly at 157-171 and
236-244), change the enable_remote_postgres default from true to false and gate
both the UFW rule that opens port 5432 and the pg_hba.conf rule append behind
this flag; update variable definition to default(false), wrap the tasks that add
the pg_hba entry and the ufw rule in when: enable_remote_postgres | bool, and
ensure any handlers or conditionals that reload/restart PostgreSQL only run when
the flag is true so remote access is opt-in and not exposed by default.

Comment on lines +88 to +104
- name: Create production env file (.env.production)
copy:
dest: "{{ app_dir }}/current/{{ env_file }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: '0600'
content: |
DEBUG=False
ALLOWED_HOSTS=.{{ domain }},{{ domain }},127.0.0.1,localhost
SECRET_KEY={{ lookup('password', '/dev/null', length=64, chars='ascii_letters,digits') }}
DATABASE_URL=postgres://{{ postgres_db_user }}:{{ postgres_db_password }}@127.0.0.1:5432/{{ postgres_db_name }}
PORT=8000
# Add any extra environment variables below
# SENTRY_DSN=
# OPENAI_API_KEY=
notify: Restart app

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Persist the Django SECRET_KEY instead of regenerating every run.

Using lookup('password', '/dev/null', ...) creates a brand-new secret on every playbook execution. That forces a service restart each time and invalidates all Django sessions/tokens, even when nothing else changed.

Please persist the generated value (e.g., write it once to a file under {{ app_dir }}/shared/secret_key and reuse it on subsequent runs) so reruns stay idempotent. Example:

-          SECRET_KEY={{ lookup('password', '/dev/null', length=64, chars='ascii_letters,digits') }}
+          SECRET_KEY={{ lookup('password', '{{ app_dir }}/shared/secret_key', length=64, chars='ascii_letters,digits') }}

This keeps the secret stable across deployments while still generating it on first run.

🤖 Prompt for AI Agents
In ansible/playbook.yml around lines 88-104, the SECRET_KEY is being regenerated
on every run using lookup('password', '/dev/null', ...), causing unnecessary
restarts and session invalidation; change the play so it first checks for a
persisted secret at {{ app_dir }}/shared/secret_key, if missing generate a new
secret and write it to that file with owner {{ app_user }} and mode 0600, then
read the secret from that file and insert its value into the .env.production
content (instead of calling lookup each run); ensure the task is idempotent
(only writes when file absent or empty) and that file permissions are secure so
subsequent playbook runs reuse the same SECRET_KEY.

Comment on lines +21 to +23
- Creates a systemd service `gunicorn-blt`.
- Nginx reverse proxies to Gunicorn on port 8000.
- Opens ports 22, 80, 443 with UFW.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Align README with the actual service (Uvicorn, not Gunicorn).

The playbook provisions a systemd unit named blt-uvicorn that runs uvicorn. The README still references a gunicorn-blt service and Gunicorn proxying, which will mislead anyone operating this deployment. Please update the doc to match the playbook.

🤖 Prompt for AI Agents
ansible/README.md lines 21-23: the README mentions a systemd service named
`gunicorn-blt` and that Nginx reverse proxies to Gunicorn on port 8000, but the
playbook actually provisions `blt-uvicorn` and runs Uvicorn; update the three
bullet points to reference the `blt-uvicorn` systemd service, state that Nginx
reverse proxies to Uvicorn (adjust port if different in the playbook, e.g., 8000
or as configured), and ensure any instructions about service names, sockets, or
systemd unit files match the playbook’s filenames and commands; also replace any
remaining “Gunicorn” mentions with “Uvicorn”.

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Oct 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

1 participant