Skip to content

ansible deployment Fixes #636#643

Merged
A1L13N merged 20 commits into
alphaonelabs:mainfrom
ishaan-arora-1:ansible-copy
Jun 27, 2025
Merged

ansible deployment Fixes #636#643
A1L13N merged 20 commits into
alphaonelabs:mainfrom
ishaan-arora-1:ansible-copy

Conversation

@ishaan-arora-1

@ishaan-arora-1 ishaan-arora-1 commented Jun 20, 2025

Copy link
Copy Markdown
Contributor

Fixes #636

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive Ansible deployment system with configuration templates for Nginx, systemd service, environment variables, and automated deployment scripts.
    • Added an Ansible playbook to automate OS setup, application deployment, and service configuration for the education website project.
  • Chores

    • Added sample environment variables to .env.sample for easier server and application configuration.
  • Bug Fixes

    • Updated Django settings to source allowed hosts and CSRF trusted origins dynamically from environment variables for flexible deployment.

@coderabbitai

coderabbitai Bot commented Jun 20, 2025

Copy link
Copy Markdown
Contributor

"""

Walkthrough

A comprehensive Ansible-based deployment system was introduced, including configuration templates for Nginx, systemd, and environment variables, as well as a main playbook for automating server setup, application deployment, and hardening. Supporting shell scripts and sample environment configurations were added, and Django settings were updated to use environment-driven lists for allowed hosts and CSRF trusted origins.

Changes

File(s) Change Summary
ansible/ansible.cfg, ansible/deploy.sh Added Ansible configuration file and a deployment shell script for running playbooks with environment variable support.
ansible/education-website.service.j2, ansible/env.j2, ansible/nginx-http.conf.j2, ansible/nginx.conf.j2, ansible/update.sh.j2 Added Jinja2 templates for systemd service, Django environment variables, Nginx HTTP/HTTPS configurations, and an update automation script.
ansible/playbook.yml Introduced a multi-stage Ansible playbook for OS bootstrapping, application deployment, and server hardening, including handlers and use of templates.
.env.sample Extended with new placeholders for deployment, project, and database configuration.
web/settings.py Changed ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS from hardcoded lists to environment-driven lists using env.list().

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant deploy.sh
    participant Ansible
    participant TargetServer

    User->>deploy.sh: Run deploy.sh
    deploy.sh->>deploy.sh: Load .env variables
    deploy.sh->>Ansible: Execute ansible-playbook playbook.yml with variables
    Ansible->>TargetServer: Connect via SSH
    Ansible->>TargetServer: Bootstrap OS (install packages, setup swap, reboot)
    Ansible->>TargetServer: Deploy application (create user, clone repo, setup venv, configure env, migrate, collectstatic)
    Ansible->>TargetServer: Harden & run (template configs, setup systemd/nginx, enable firewall)
    Ansible-->>User: Deployment complete
Loading

Assessment against linked issues

Objective Addressed Explanation
Implement Ansible-based deployment system with playbook, templates, and automation scripts (#636)
Provide configuration for Nginx, systemd, and environment variables via templates (#636)
Add deployment and update scripts for automation (#636)
Update Django settings to allow new server IP (#636)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes were found.
"""

✨ Finishing Touches
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary of Changes

Hello @ishaan-arora-1, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a robust and automated Ansible deployment solution for the 'education-website' project. It streamlines the entire deployment lifecycle, from initial server setup and dependency installation to application configuration, database management, Nginx web serving, and security hardening. The changes aim to provide a reliable and repeatable process for deploying and maintaining the application, addressing the need for a comprehensive deployment strategy.

Highlights

  • Comprehensive Ansible Deployment: This pull request introduces a complete and automated Ansible-based deployment system for the 'education-website' project, covering server provisioning, application setup, and ongoing maintenance.
  • Automated Server & Application Setup: The main playbook (playbook.yml) automates the installation of system dependencies (Python 3.11, PostgreSQL, Nginx, UFW, fail2ban, Certbot), cloning the application repository, setting up a Python virtual environment, installing dependencies, and configuring the Django application (migrations, static files).
  • Nginx and HTTPS Configuration: Templated Nginx configurations are provided to serve the application, supporting both HTTP and HTTPS with integrated Let's Encrypt (Certbot) for SSL certificate management, including static and media file serving.
  • Database Management and Utilities: Dedicated playbooks are included for PostgreSQL database and user creation, as well as a 'fix_db.yml' for destructively resetting the database. Additional utility playbooks facilitate code updates, server reboots, and various site health checks.
  • Detailed Documentation: A new README.md file offers extensive documentation, outlining prerequisites, environment variables, usage instructions for various playbooks, troubleshooting tips, and security considerations, making the deployment process clear and manageable.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive set of Ansible playbooks for automating the deployment of the education-website project. This is a fantastic addition that will significantly improve the deployment process.

My review focuses on improving security, correctness, and maintainability by adhering to Ansible and general infrastructure best practices. I've identified several critical and high-severity issues that should be addressed:

  • Security: The most critical issues involve hardcoded credentials in the inventory file, an insecure default superuser password, and disabling SSH host key verification.
  • Correctness: There's a misconfigured Nginx proxy_pass directive that will likely cause parts of the application to fail, and running makemigrations on a production server is a risky practice.
  • Maintainability & Best Practices: I've suggested several improvements, such as avoiding hardcoded paths (e.g., /root), making tasks more idempotent (e.g., venv creation), and using dedicated Ansible modules (pip) instead of shell where appropriate.

Addressing these points will make the Ansible deployment process more secure, robust, and easier to maintain. Overall, this is a great start, and with these changes, it will be a solid foundation for automated deployments.

Comment thread ansible/fix_db.yml Outdated
Comment thread ansible/hosts Outdated
Comment thread ansible/nginx.conf.j2 Outdated
Comment thread ansible/ansible.cfg Outdated
Comment thread ansible/education-website.service.j2 Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/update.sh.j2 Outdated
Comment thread ansible/migrate.yml Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 37

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 459997a and c25bf85.

📒 Files selected for processing (16)
  • ansible/README.md (1 hunks)
  • ansible/ansible.cfg (1 hunks)
  • ansible/deploy.sh (1 hunks)
  • ansible/education-website.service.j2 (1 hunks)
  • ansible/env.j2 (1 hunks)
  • ansible/fix_db.yml (1 hunks)
  • ansible/hosts (1 hunks)
  • ansible/migrate.yml (1 hunks)
  • ansible/nginx-http.conf.j2 (1 hunks)
  • ansible/nginx.conf.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • ansible/quick_test.yml (1 hunks)
  • ansible/reboot.yml (1 hunks)
  • ansible/simple_test.yml (1 hunks)
  • ansible/test_site.yml (1 hunks)
  • ansible/update.sh.j2 (1 hunks)
🧰 Additional context used
🪛 Checkov (3.2.334)
ansible/test_site.yml

[MEDIUM] 31-41: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)


[MEDIUM] 40-49: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)

ansible/simple_test.yml

[MEDIUM] 54-64: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)


[MEDIUM] 63-72: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)

🪛 YAMLlint (1.37.1)
ansible/test_site.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 37-37: truthy value should be one of [false, true]

(truthy)


[warning] 46-46: truthy value should be one of [false, true]

(truthy)


[warning] 60-60: truthy value should be one of [false, true]

(truthy)


[warning] 69-69: truthy value should be one of [false, true]

(truthy)


[error] 73-73: no new line character at the end of file

(new-line-at-end-of-file)

ansible/reboot.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 28-28: truthy value should be one of [false, true]

(truthy)


[warning] 41-41: truthy value should be one of [false, true]

(truthy)


[error] 47-47: no new line character at the end of file

(new-line-at-end-of-file)

ansible/simple_test.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 18-18: truthy value should be one of [false, true]

(truthy)


[warning] 29-29: truthy value should be one of [false, true]

(truthy)


[warning] 60-60: truthy value should be one of [false, true]

(truthy)


[warning] 69-69: truthy value should be one of [false, true]

(truthy)


[error] 78-78: no new line character at the end of file

(new-line-at-end-of-file)

ansible/migrate.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 14-14: truthy value should be one of [false, true]

(truthy)


[warning] 15-15: truthy value should be one of [false, true]

(truthy)


[warning] 80-80: truthy value should be one of [false, true]

(truthy)


[error] 80-80: no new line character at the end of file

(new-line-at-end-of-file)

ansible/fix_db.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 21-21: truthy value should be one of [false, true]

(truthy)


[warning] 42-42: truthy value should be one of [false, true]

(truthy)


[warning] 98-98: truthy value should be one of [false, true]

(truthy)


[warning] 131-131: truthy value should be one of [false, true]

(truthy)


[warning] 136-136: truthy value should be one of [false, true]

(truthy)


[error] 140-140: no new line character at the end of file

(new-line-at-end-of-file)

ansible/quick_test.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 52-52: truthy value should be one of [false, true]

(truthy)


[warning] 63-63: truthy value should be one of [false, true]

(truthy)


[warning] 84-84: truthy value should be one of [false, true]

(truthy)


[warning] 114-114: truthy value should be one of [false, true]

(truthy)


[error] 125-125: no new line character at the end of file

(new-line-at-end-of-file)

ansible/playbook.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 33-33: truthy value should be one of [false, true]

(truthy)


[warning] 71-71: truthy value should be one of [false, true]

(truthy)


[warning] 72-72: truthy value should be one of [false, true]

(truthy)


[warning] 73-73: truthy value should be one of [false, true]

(truthy)


[warning] 101-101: truthy value should be one of [false, true]

(truthy)


[warning] 128-128: truthy value should be one of [false, true]

(truthy)


[warning] 136-136: truthy value should be one of [false, true]

(truthy)


[warning] 151-151: truthy value should be one of [false, true]

(truthy)


[warning] 211-211: truthy value should be one of [false, true]

(truthy)


[warning] 217-217: truthy value should be one of [false, true]

(truthy)


[warning] 271-271: truthy value should be one of [false, true]

(truthy)


[warning] 273-273: truthy value should be one of [false, true]

(truthy)


[error] 310-310: no new line character at the end of file

(new-line-at-end-of-file)

🪛 markdownlint-cli2 (0.17.2)
ansible/README.md

45-45: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


47-47: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


54-54: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


62-62: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


64-64: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


68-68: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


70-70: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


74-74: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


76-76: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


80-80: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


82-82: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


86-86: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


88-88: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


94-94: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


95-95: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


98-98: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


99-99: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


101-101: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


102-102: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


104-104: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


105-105: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


120-120: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


120-120: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


121-121: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


125-125: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


125-125: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


126-126: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


131-131: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


131-131: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


132-132: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


137-137: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


137-137: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


138-138: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


147-147: Files should end with a single newline character
null

(MD047, single-trailing-newline)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run Tests
🔇 Additional comments (13)
ansible/update.sh.j2 (1)

8-8: Verify systemctl permissions for the deployment user.

The script uses sudo systemctl restart but this may not be necessary if the deployment user has proper systemd permissions configured.

Check if the deployment user has systemctl permissions for the service:

#!/bin/bash
# Check if user has systemctl permissions without sudo
sudo -u "{{ vps_user }}" systemctl status "{{ project_name }}" 2>/dev/null && echo "User has systemctl permissions" || echo "User needs sudo for systemctl"

# Check sudoers configuration for systemctl
grep -r "systemctl" /etc/sudoers.d/ || echo "No specific systemctl sudo rules found"
ansible/reboot.yml (2)

1-3: Playbook header structure is correct
Your playbook correctly defines a list with - hosts: production after ---. No change needed.


5-6: Ignore Jinja boolean syntax warnings
The true in default(..., true) is a Jinja boolean, not a YAML boolean. You can safely ignore the YAMLLint truthy value warnings here.

ansible/hosts (1)

8-10: Interpreter path specification is good
Defining ansible_python_interpreter=/usr/bin/python3 ensures Python 3 is used on the remote host.

ansible/test_site.yml (1)

31-38: Enforce HTTPS for domain URI tests
Checkov flags HTTPS enforcement. Your conditional uses https:// for domain_name, but verify SSL availability or add validate_certs: no if using self-signed certificates to prevent false negatives.

ansible/simple_test.yml (2)

11-18: Verify copy.src path resolution
Using src: ../test_db.py may fail if the playbook is run from a different location. Prefer src: "{{ playbook_dir }}/test_db.py" or adjust the file path to match your repository layout.


54-62: Ensure HTTPS usage in URI tests
Checkov flags requiring HTTPS in URI calls. Confirm SSL support on your domain or configure validate_certs to prevent test failures with self-signed certs.

ansible/nginx-http.conf.j2 (2)

1-5: Validate Nginx user directive
user www-data; is Debian/Ubuntu-specific. Ensure this matches your target OS or make it configurable if you support multiple distributions.


27-29: Confirm Jinja method-call support
Using domain_name.startswith('www.') invokes a Python method in Jinja. Verify this is allowed in your Ansible/Jinja sandbox; if not, use a regex test (domain_name is match("^www\\.")).

ansible/playbook.yml (4)

66-75: Verify intentional use of force: yes in your Git clone
force: yes will discard any local changes in /root/{{ project_name }} on each run. Confirm this is acceptable for your deployment workflow.


97-117: Avoid creating a root Postgres user by default
The default db_user is root. Best practice is to create a dedicated, minimally-privileged user (e.g. {{ project_name }}) and require a strong db_password.


220-261: Clarify Nginx behavior when domain_name is empty
All Nginx templates are conditional on domain_name != "". If no domain is provided, Nginx is never configured. Verify this is intended, or provide a default HTTP‐only config.


291-310: Handlers look solid
Restarting Nginx, the app service, PostgreSQL, and Fail2Ban covers all changes.

Comment thread ansible/ansible.cfg Outdated
Comment thread ansible/education-website.service.j2 Outdated
Comment thread ansible/education-website.service.j2 Outdated
Comment thread ansible/education-website.service.j2 Outdated
Comment thread ansible/env.j2 Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 23

♻️ Duplicate comments (22)
ansible/hosts (1)

1-4: Remove hardcoded credentials from inventory

Storing the VPS IP and SSH password in plain text is a serious security risk. Use Ansible Vault or environment-variable lookups (as shown in the commented example) instead of hardcoding.

ansible/ansible.cfg (1)

4-4: Re-enable SSH host key checking

Disabling host_key_checking exposes you to MITM attacks. It’s recommended to set it back to True and manage known_hosts via ssh-keyscan or manual SSH to your server.

ansible/deploy.sh (1)

1-2: Enhance script robustness with error handling

Add strict mode and logging to catch failures early:

 #!/bin/bash
+set -euo pipefail
+
 cd "$(dirname "$0")" && ansible-playbook -i hosts playbook.yml
ansible/update.sh.j2 (4)

1-1: Enable strict error handling
Add set -euo pipefail immediately after the shebang to fail on errors and unset variables.


2-2: Parameterize the project directory
Avoid hardcoding /root/{{ project_name }}. Define e.g.

PROJECT_DIR="/home/{{ vps_user }}/{{ project_name }}"
cd "$PROJECT_DIR"

and use $PROJECT_DIR throughout.


3-7: Add logging and error checks for each step
Wrap critical commands (git pull, pip install, manage.py migrate, collectstatic) with echo for context and verify exit codes to prevent silent failures.


8-8: Remove use of sudo for service restart
Either run this script as root or rely on Ansible’s privilege escalation. Drop sudo and call systemctl restart {{ project_name }} directly.

ansible/education-website.service.j2 (1)

6-10: Avoid running service from /root and harden the unit

  • Change WorkingDirectory and Environment to /home/{{ vps_user }}/{{ project_name }}.
  • Add security directives under [Service]:
    NoNewPrivileges=true
    ProtectSystem=strict
    ProtectHome=true
    ReadWritePaths=/home/{{ vps_user }}/{{ project_name }}
    PrivateTmp=true
ansible/reboot.yml (4)

2-3: Explicitly declare gather_facts
Add gather_facts: no (or yes if needed) under the play to clarify fact gathering and improve performance.


8-16: Register reboot task output for visibility
Use register: reboot_result on the reboot task and a subsequent debug: var=reboot_result to capture and display results.


26-28: Explicitly specify service unit names
Change name: "{{ item }}" to name: "{{ item }}.service" in the systemd task to avoid ambiguity.


35-41: Use the systemd module instead of raw commands
Replace the command: systemctl status {{ item }} block with the Ansible systemd module for idempotent status checks.

ansible/test_site.yml (2)

11-20: Use django_manage module for Django shell commands
Replace the raw shell task invoking manage.py shell with community.general.django_manage for structured, idempotent execution.


31-38: Parameterize the HTTP scheme and domain URL
Introduce a uri_scheme variable to avoid inlining https:///http://, and ensure HTTPS is used when domain_name is set.

ansible/simple_test.yml (1)

37-44: Leverage the django_manage module for DB connectivity checks
Replace the inline shell task running manage.py shell with community.general.django_manage to run the DB connection check idempotently.

ansible/nginx-http.conf.j2 (1)

37-43: Avoid /root for static and media aliases
Serving files from /root/... can lead to permission and security issues. Use a non-root directory such as /var/www/{{ project_name }}/static and /var/www/{{ project_name }}/media.

ansible/nginx.conf.j2 (2)

57-63: Duplicate /root alias for static/media
As before, avoid serving from /root/.... Use /var/www/{{ project_name }}/static and /var/www/{{ project_name }}/media.


65-89: Fix hardcoded SSE socket path
The SSE endpoint’s proxy_pass references /home/django/gemnar-website/gemnar.sock, pointing to the wrong project and conflicting with the TCP upstream. Update it to use http://127.0.0.1:{{ app_port }} or the correct socket for education-website.

Apply this diff:

-    proxy_pass http://unix:/home/django/gemnar-website/gemnar.sock;
+    proxy_pass http://127.0.0.1:{{ app_port }};
ansible/fix_db.yml (1)

81-89: Replace hardcoded superuser password
Hardcoding 'admin123' is a security risk. Retrieve the password from an environment variable or Ansible Vault (e.g., {{ lookup('env', 'ADMIN_PASSWORD') }}).

Apply this diff:

-        User.objects.create_superuser('admin', 'admin@{{ project_name }}.com', 'admin123');
+        User.objects.create_superuser(
+            'admin',
+            'admin@{{ project_name }}.com',
+            '{{ lookup("env", "ADMIN_PASSWORD") | default("change_me_immediately", true) }}'
+        );
ansible/README.md (3)

45-51: ** Fix blank lines around headings and code blocks**

Several sub-section headings (e.g., ### 1. Main Deployment) and fenced code blocks lack blank lines before or after, impacting readability and violating MD022/MD031. Please insert blank lines to surround these elements.

Also applies to: 62-66


120-121: ** Remove trailing colons from headings**

Headings such as ### Check service status: should not end with a colon (MD026). Remove the trailing punctuation.

Also applies to: 125-126, 131-132, 137-138


148-148: ** Ensure newline at end of file**

Add a single newline at the end of the file to comply with POSIX standards and avoid MD047 warnings.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c25bf85 and 9d1dc4a.

📒 Files selected for processing (17)
  • .env.sample (1 hunks)
  • ansible/README.md (1 hunks)
  • ansible/ansible.cfg (1 hunks)
  • ansible/deploy.sh (1 hunks)
  • ansible/education-website.service.j2 (1 hunks)
  • ansible/env.j2 (1 hunks)
  • ansible/fix_db.yml (1 hunks)
  • ansible/hosts (1 hunks)
  • ansible/migrate.yml (1 hunks)
  • ansible/nginx-http.conf.j2 (1 hunks)
  • ansible/nginx.conf.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • ansible/quick_test.yml (1 hunks)
  • ansible/reboot.yml (1 hunks)
  • ansible/simple_test.yml (1 hunks)
  • ansible/test_site.yml (1 hunks)
  • ansible/update.sh.j2 (1 hunks)
🧰 Additional context used
🪛 dotenv-linter (3.3.0)
.env.sample

[warning] 34-34: [UnorderedKey] The VPS_PASSWORD key should go before the VPS_USER key


[warning] 35-35: [UnorderedKey] The PROJECT_NAME key should go before the VPS_IP key


[warning] 36-36: [UnorderedKey] The DOMAIN_NAME key should go before the PROJECT_NAME key


[warning] 37-37: [UnorderedKey] The APP_PORT key should go before the DOMAIN_NAME key


[warning] 38-38: [UnorderedKey] The PYTHON_VERSION key should go before the VPS_IP key


[warning] 39-39: [UnorderedKey] The ENABLE_HTTPS_REDIRECT key should go before the PROJECT_NAME key


[warning] 43-43: [EndingBlankLine] No blank line at the end of the file


[warning] 43-43: [UnorderedKey] The DB_PASSWORD key should go before the DB_USER key

🪛 GitHub Actions: Test
.env.sample

[error] 1-1: Pre-commit hook 'end-of-file-fixer' failed and modified the file to fix end of file issues.

🪛 Checkov (3.2.334)
ansible/test_site.yml

[MEDIUM] 31-41: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)


[MEDIUM] 40-49: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)

ansible/simple_test.yml

[MEDIUM] 54-64: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)


[MEDIUM] 63-72: Ensure that HTTPS url is used with uri

(CKV2_ANSIBLE_1)

🪛 markdownlint-cli2 (0.17.2)
ansible/README.md

45-45: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


47-47: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


54-54: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


62-62: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


64-64: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


68-68: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


70-70: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


74-74: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


76-76: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


80-80: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


82-82: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


86-86: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


88-88: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


94-94: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


95-95: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


98-98: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


99-99: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


101-101: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


102-102: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


104-104: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


105-105: Lists should be surrounded by blank lines
null

(MD032, blanks-around-lists)


120-120: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


120-120: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


121-121: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


125-125: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


125-125: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


126-126: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


131-131: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


131-131: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


132-132: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)


137-137: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


137-137: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


138-138: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)

🔇 Additional comments (11)
ansible/hosts (2)

5-7: Good: Environment-variable driven credential example

The commented alternative using lookup('env', ...) is the recommended approach. Ensure you enable this and remove the hardcoded line.


8-11: Inventory vars for Python interpreter look correct

Setting ansible_python_interpreter under [production:vars] ensures the correct interpreter is used.

ansible/ansible.cfg (2)

1-3: Defaults inventory and pipelining configuration looks good

Using hosts as the default inventory and enabling pipelining improves performance.


5-9: SSH connection retries and interpreter path are correctly configured

The retry setting and Python interpreter path are appropriate for reliable connections.

ansible/nginx-http.conf.j2 (3)

27-31: Conditional server_name block is well-implemented
The templated logic correctly handles domain_name, including www. variants and fallback to _.


33-35: ACME challenge location is standard
The .well-known/acme-challenge path and root are correctly configured for Let's Encrypt HTTP-01 validation.


45-51: Proxy settings look good
The proxy_pass directive and header forwarding are correctly configured for your upstream application.

ansible/nginx.conf.j2 (3)

32-44: HTTP-to-HTTPS redirection block is correct
The port 80 server block with ACME challenge and 301 redirect is properly implemented.


51-56: SSL configuration aligns with Certbot best practices
Certificates, DH params, and recommended options are correctly included.


91-99: Main proxy block is correctly configured
The fallback location / with TCP proxy_pass and buffering settings suits general request handling.

ansible/quick_test.yml (1)

1-3: The playbook header (---, hosts, become) is correctly structured; no changes needed.

Comment thread .env.sample Outdated
Comment thread ansible/env.j2
Comment thread ansible/env.j2
Comment thread ansible/env.j2 Outdated
Comment thread ansible/test_site.yml Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml Outdated
Comment thread ansible/playbook.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (8)
ansible/env.j2 (3)

4-4: Make ENVIRONMENT dynamic instead of hardcoded


19-19: Use a dynamic path for STATIC_ROOT


68-70: Construct SERVICE_ACCOUNT_FILE path dynamically

ansible/playbook.yml (5)

86-86: Require secure defaults for sensitive variables


108-108: Require secure defaults for sensitive variables


105-105: Avoid using root as default for users


119-127: Use the pip module for dependencies and notify service restart


2-4: Add descriptive play name and standardize boolean literals

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1dc4a and 02aae31.

📒 Files selected for processing (5)
  • ansible/cleanup.yml (1 hunks)
  • ansible/env.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • ansible/wait_for_vps.sh (1 hunks)
  • web/settings.py (1 hunks)
🧰 Additional context used
🪛 Shellcheck (0.10.0)
ansible/wait_for_vps.sh

[style] 9-9: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 9-9: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 24-24: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 24-24: $/${} is unnecessary on arithmetic variables.

(SC2004)

🪛 YAMLlint (1.37.1)
ansible/cleanup.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[warning] 9-9: truthy value should be one of [false, true]

(truthy)


[warning] 10-10: truthy value should be one of [false, true]

(truthy)


[warning] 34-34: truthy value should be one of [false, true]

(truthy)


[warning] 40-40: truthy value should be one of [false, true]

(truthy)


[warning] 41-41: truthy value should be one of [false, true]

(truthy)


[warning] 48-48: truthy value should be one of [false, true]

(truthy)


[warning] 55-55: truthy value should be one of [false, true]

(truthy)


[warning] 59-59: truthy value should be one of [false, true]

(truthy)


[warning] 65-65: truthy value should be one of [false, true]

(truthy)


[error] 74-74: no new line character at the end of file

(new-line-at-end-of-file)


[error] 74-74: trailing spaces

(trailing-spaces)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run Tests
  • GitHub Check: build-and-test
🔇 Additional comments (4)
web/settings.py (1)

77-77: LGTM - Production server IP correctly added

The addition of the production server IP address aligns with the Ansible deployment configuration and ensures the Django application accepts requests from the deployment target.

ansible/wait_for_vps.sh (1)

11-22: Well-structured retry logic with good user feedback

The script provides excellent user experience with clear progress indicators and appropriate timeout handling.

ansible/cleanup.yml (1)

5-74: Comprehensive cleanup logic with appropriate error handling

The playbook covers all necessary cleanup tasks and correctly uses ignore_errors to ensure cleanup continues even when some resources don't exist.

ansible/playbook.yml (1)

61-83: Well-structured environment variable parsing logic

The multi-step approach to parse .env file contents into Ansible variables is well-implemented with proper error handling and clear variable transformation.

Comment thread ansible/wait_for_vps.sh Outdated
Comment thread ansible/cleanup.yml Outdated
Comment thread ansible/cleanup.yml Outdated
Comment thread ansible/cleanup.yml Outdated
Comment thread ansible/playbook.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🔭 Outside diff range comments (2)
ansible/playbook.yml (2)

175-180: Use consistent boolean literals & prefer reload
Replace enabled: yes with enabled: true and consider state: reloaded for Nginx to minimize disruption:

-      service: {name: nginx, state: restarted, enabled: yes}
+      service: {name: nginx, state: reloaded, enabled: true}

180-180: Add newline at end of file
Ensure the file ends with a newline to satisfy YAML parsers and avoid warnings.

♻️ Duplicate comments (5)
ansible/playbook.yml (5)

86-114: Remove insecure defaults & non-root defaults
Variables like SECRET_KEY and DB_PASSWORD default to change_me, and VPS_USER/DB_USER default to root. This allows insecure deployments and violates least‐privilege. Require these values explicitly instead of providing fallbacks.


122-131: Leverage Ansible modules for venv, pip & notifications
Replace the shell-based venv creation and pip install with ansible.builtin.python3_virtualenv and ansible.builtin.pip, and add notify: restart app to automatically reload the service on dependency changes.


138-147: Split Django management commands for clarity
Combine migrations and collectstatic make debugging harder. Split into two tasks or use community.general.django_manage for idempotent operations and better error isolation.


165-174: Tag firewall tasks for selective execution
Assign tags: firewall to UFW tasks so you can target or skip firewall rules in automation runs.


17-24: Tag dependency installation for selective runs
Add a tags: attribute (e.g., dependencies) to the apt task. This allows you to run or skip dependency installation independently in CI or troubleshooting.

-    - name: Install base packages (non-interactive, synchronous)
+    - name: Install base packages (non-interactive, synchronous)
+      tags: dependencies
       apt:
         name: "{{ pkgs }}"
         state: present
         update_cache: yes
         cache_valid_time: 3600
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02aae31 and 2430f75.

📒 Files selected for processing (1)
  • ansible/playbook.yml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build-and-test
  • GitHub Check: Run Tests
🔇 Additional comments (1)
ansible/playbook.yml (1)

132-137: No changes needed here. This template task is correct as-is.

Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (6)
ansible/env.j2 (3)

4-4: Make ENVIRONMENT dynamic instead of hardcoding
Hardcoding ENVIRONMENT="production" limits flexibility for staging or testing; switch to a Jinja2 variable with a default.


19-19: Use a non-root path for static files
Serving from /root/... can lead to permission and security issues. Reference a deploy user’s home or /var/www:

STATIC_ROOT="/home/{{ vps_user }}/{{ project_name }}/staticfiles"

68-70: Construct SERVICE_ACCOUNT_FILE path dynamically
The service account path is currently taken verbatim. Prefix it with the deployment directory or use vps_user and project_name to ensure correct resolution.

ansible/nginx-http.conf.j2 (1)

37-43: Avoid serving static/media from /root
Aliasing /static/ and /media/ to /root/{{ project_name }} can cause permission and security problems. Use a non-root directory (e.g. /var/www/{{ project_name }}/static or /home/{{ vps_user }}/{{ project_name }}/staticfiles).

ansible/nginx.conf.j2 (2)

57-63: Avoid serving static/media from /root
Both the HTTPS server and HTTP-only fallback alias /static/ and /media/ to /root/{{ project_name }}. Adjust these to a non-root path (e.g. /var/www/{{ project_name }}/static, or use vps_user).


113-119: Avoid serving static/media from /root
In the non-HTTPS server block, static and media are still aliased under /root/{{ project_name }}. Update to a secure, non-root path.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2430f75 and 5407fb8.

📒 Files selected for processing (3)
  • ansible/env.j2 (1 hunks)
  • ansible/nginx-http.conf.j2 (1 hunks)
  • ansible/nginx.conf.j2 (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Analyze (python)
  • GitHub Check: Run Tests
  • GitHub Check: build-and-test

Comment thread ansible/env.j2
Comment thread ansible/env.j2
Comment thread ansible/nginx.conf.j2 Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (14)
ansible/env.j2 (4)

2-4: Remove quotation marks and template ENVIRONMENT dynamically
Quoting Jinja placeholders embeds literal quotes in the generated .env. Switch to unquoted substitutions for SECRET_KEY and DEBUG, and parameterize ENVIRONMENT via a variable:

- SECRET_KEY="{{ secret_key }}"
- DEBUG="{{ debug }}"
- ENVIRONMENT="production"
+ SECRET_KEY={{ secret_key }}
+ DEBUG={{ debug }}
+ ENVIRONMENT={{ environment | default('production') }}

19-19: Make STATIC_ROOT dynamic
Using /root assumes the root user; switch to the deploy user and project variables:

- STATIC_ROOT="/root/{{ project_name }}/staticfiles"
+ STATIC_ROOT="/home/{{ vps_user }}/{{ project_name }}/staticfiles"

68-70: Construct SERVICE_ACCOUNT_FILE path dynamically
Ensure the service account file path uses the correct deployment directory:

- SERVICE_ACCOUNT_FILE="{{ service_account_file }}"
+ SERVICE_ACCOUNT_FILE="/home/{{ vps_user }}/{{ project_name }}/{{ service_account_file }}"

12-16: Parameterize DB_HOST and DB_PORT
Hardcoded localhost and 5432 reduce flexibility. Template these from variables:

- DB_HOST="localhost"
- DB_PORT="5432"
+ DB_HOST={{ db_host | default('localhost') }}
+ DB_PORT={{ db_port | default('5432') }}
ansible/wait_for_vps.sh (2)

29-29: Simplify arithmetic expansions
Same change applies here:

- echo "⚠️  VPS still unreachable after $(($MAX_ATTEMPTS * $WAIT_TIME / 60)) minutes."
+ echo "⚠️  VPS still unreachable after $((MAX_ATTEMPTS * WAIT_TIME / 60)) minutes."

14-15: Simplify arithmetic expansions
Remove unnecessary ${} inside $(( )):

- echo "⏰ Will check every $WAIT_TIME seconds for up to $(($MAX_ATTEMPTS * $WAIT_TIME / 60)) minutes"
+ echo "⏰ Will check every $WAIT_TIME seconds for up to $((MAX_ATTEMPTS * WAIT_TIME / 60)) minutes"
ansible/deploy.sh (1)

2-3: Enable full strict mode
Extend set -e to catch unset variables and pipeline failures:

set -euo pipefail
IFS=$'\n\t'
ansible/playbook.yml (7)

115-121: Optimize Git clone performance
Enable shallow cloning to reduce clone time and bandwidth:

- git:
+ git:
     repo: "{{ git_repo }}"
     dest: "{{ project_root }}"
     version: "{{ git_branch }}"
+    depth: 1
     force: yes

107-110: Use least privilege for database user
Defaulting db_user to root is a security risk. Require it or use a non-privileged user:

- db_user: "{{ env_vars.DB_USER | default('root') }}"
+ db_user: "{{ env_vars.DB_USER }}"

153-157: Validate Nginx configuration before deployment
Add a validate argument to catch syntax errors:

- template:
    src: nginx-http.conf.j2
    dest: /etc/nginx/nginx.conf
+ template:
    src: nginx-http.conf.j2
    dest: /etc/nginx/nginx.conf
+   validate: 'nginx -t -c %s'

123-131: Leverage Ansible modules for virtualenv and pip
Replace shell tasks with idempotent modules:

- shell: |
-   python3.11 -m venv venv
-   source venv/bin/activate
-   pip install --upgrade pip wheel
-   pip install -r requirements.txt uvicorn gunicorn psycopg2-binary
+ ansible.builtin.python3_virtualenv:
+   path: "{{ project_root }}/venv"
+   python: python3.11
+   clear: true
+
+ ansible.builtin.pip:
+   requirements: "{{ project_root }}/requirements.txt"
+   virtualenv: "{{ project_root }}/venv"
+   extra_args: uvicorn gunicorn psycopg2-binary

165-174: Tag and consolidate firewall tasks
Group UFW rules under a tag and combine similar tasks:

- - name: Allow ports
-   ufw:
-     rule: allow
-     port: "{{ item }}"
-   loop: [22, 80, 443]
+ - name: Configure firewall for required ports
+   ufw:
+     rule: allow
+     port: "{{ item }}"
+   loop: [22, 80, 443]
+   tags: firewall

86-96: Avoid insecure defaults for sensitive variables
Relying on placeholders like "change_me" for secret_key and db_password risks insecure deployments. Remove the defaults:

- secret_key: "{{ env_vars.SECRET_KEY | default('change_me') }}"
+ secret_key: "{{ env_vars.SECRET_KEY }}"

159-164: Reload systemd daemon after updating service file
Ensure systemd notices unit changes before restart:

- template:
    src: education-website.service.j2
    dest: /etc/systemd/system/education-website.service
+ template:
    src: education-website.service.j2
    dest: /etc/systemd/system/education-website.service
+   args:
+     daemon_reload: yes
      notify: restart app
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5407fb8 and 4620815.

📒 Files selected for processing (4)
  • ansible/deploy.sh (1 hunks)
  • ansible/env.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • ansible/wait_for_vps.sh (1 hunks)
🧰 Additional context used
🪛 Shellcheck (0.10.0)
ansible/deploy.sh

[info] 11-11: Not following: ./.env was not specified as input (see shellcheck -x).

(SC1091)

ansible/wait_for_vps.sh

[style] 14-14: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 14-14: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 29-29: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 29-29: $/${} is unnecessary on arithmetic variables.

(SC2004)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run Tests
  • GitHub Check: build-and-test

Comment thread ansible/env.j2 Outdated
Comment thread ansible/wait_for_vps.sh Outdated
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 15

♻️ Duplicate comments (6)
.env.sample (1)

32-43: Add missing trailing newline at end of file
A trailing newline is needed to satisfy dotenv-linter and ensure a blank line at EOF.

Apply this diff:

 DB_PASSWORD=your_secure_database_password
+
ansible/env.j2 (3)

4-4: Make ENVIRONMENT dynamic instead of hardcoded
Allow the environment to be configured via a variable:

- ENVIRONMENT="production"
+ ENVIRONMENT={{ environment | default('production') }}

17-17: Use dynamic path for STATIC_ROOT
Reflect the actual deployment user’s home directory:

- STATIC_ROOT="/root/{{ project_name }}/staticfiles"
+ STATIC_ROOT="/home/{{ vps_user }}/{{ project_name }}/staticfiles"

66-68: Construct SERVICE_ACCOUNT_FILE path dynamically
Prefix with the deployment directory for correct file resolution:

- SERVICE_ACCOUNT_FILE="{{ service_account_file }}"
+ SERVICE_ACCOUNT_FILE="/home/{{ vps_user }}/{{ project_name }}/{{ service_account_file }}"
ansible/playbook.yml (2)

108-111: Require DB_PASSWORD input by removing default
An insecure fallback of 'change_me' makes deployments vulnerable. Fail if DB_PASSWORD is unset:

- db_password: "{{ env_vars.DB_PASSWORD | default('change_me') }}"
+ db_password: "{{ env_vars.DB_PASSWORD }}"

123-131: Use native modules for virtualenv and pip
Switch from shell to ansible.builtin.python3_virtualenv and ansible.builtin.pip for idempotency:

- shell: |
-   python3.11 -m venv venv
-   source venv/bin/activate
-   pip install -r requirements.txt uvicorn gunicorn psycopg2-binary
+ ansible.builtin.python3_virtualenv:
+   path: "{{ project_root }}/venv"
+   python: python3.11
+   clear: true
+ ansible.builtin.pip:
+   requirements: "{{ project_root }}/requirements.txt"
+   virtualenv: "{{ project_root }}/venv"
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4620815 and 27794a5.

📒 Files selected for processing (5)
  • .env.sample (1 hunks)
  • ansible/env.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • ansible/wait_for_vps.sh (1 hunks)
  • web/settings.py (1 hunks)
🧰 Additional context used
🪛 dotenv-linter (3.3.0)
.env.sample

[warning] 34-34: [UnorderedKey] The VPS_PASSWORD key should go before the VPS_USER key


[warning] 35-35: [UnorderedKey] The PROJECT_NAME key should go before the VPS_IP key


[warning] 36-36: [UnorderedKey] The DOMAIN_NAME key should go before the PROJECT_NAME key


[warning] 37-37: [UnorderedKey] The APP_PORT key should go before the DOMAIN_NAME key


[warning] 38-38: [UnorderedKey] The PYTHON_VERSION key should go before the VPS_IP key


[warning] 39-39: [UnorderedKey] The ENABLE_HTTPS_REDIRECT key should go before the PROJECT_NAME key


[warning] 43-43: [UnorderedKey] The DB_PASSWORD key should go before the DB_USER key

🪛 Shellcheck (0.10.0)
ansible/wait_for_vps.sh

[style] 14-14: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 14-14: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 29-29: $/${} is unnecessary on arithmetic variables.

(SC2004)


[style] 29-29: $/${} is unnecessary on arithmetic variables.

(SC2004)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run Tests
  • GitHub Check: build-and-test
🔇 Additional comments (1)
web/settings.py (1)

76-84: Add IP to CSRF_TRUSTED_ORIGINS
You’ve added "108.61.87.176" to ALLOWED_HOSTS but not to CSRF_TRUSTED_ORIGINS. Include it (with the correct scheme and port) to avoid CSRF errors:

- "https://alphaonelabs.com",
+ "https://108.61.87.176",
  # ...

Comment thread ansible/env.j2
Comment thread ansible/env.j2
Comment thread ansible/wait_for_vps.sh Outdated
Comment thread ansible/wait_for_vps.sh Outdated
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
ansible/nginx.conf.j2 (1)

67-67: CRITICAL: Fix the hardcoded proxy_pass for SSE endpoint.

This hardcoded proxy_pass to a Unix socket for "gemnar-website" is the exact same issue flagged in previous reviews but remains unfixed. It will cause SSE endpoint requests to fail.

Apply this fix immediately:

-            proxy_pass http://unix:/home/django/gemnar-website/gemnar.sock;
+            proxy_pass http://127.0.0.1:{{ app_port }};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27794a5 and fc23507.

📒 Files selected for processing (1)
  • ansible/nginx.conf.j2 (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build-and-test
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Run Tests
🔇 Additional comments (4)
ansible/nginx.conf.j2 (4)

1-31: LGTM: Well-configured Nginx foundation.

The basic Nginx configuration follows best practices with modern SSL protocols, appropriate worker settings, and standard HTTP optimizations.


32-45: LGTM: Proper HTTPS redirection setup.

The HTTP to HTTPS redirect configuration correctly handles ACME challenges for Let's Encrypt and implements proper 301 redirects.


65-89: LGTM: Comprehensive SSE configuration.

The Server-Sent Events configuration is well-implemented with appropriate headers, timeouts, and buffering settings for real-time log streaming.


104-129: LGTM: Consistent HTTP-only fallback configuration.

The HTTP-only server block provides proper fallback functionality with consistent proxy settings and file serving configuration.

Comment thread ansible/nginx.conf.j2 Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🔭 Outside diff range comments (1)
ansible/playbook.yml (1)

196-200: Use YAML booleans (true/false) consistently
Handlers use enabled: yes—update these to enabled: true to satisfy linters:

- name: restart nginx
  service: { name: nginx, state: restarted, enabled: true }

- name: restart app
  service: { name: education-website, state: restarted, enabled: true }
♻️ Duplicate comments (7)
ansible/env.j2 (4)

2-3: Critical: Remove quotes around Jinja2 placeholders to prevent parsing issues.

The quotes around Jinja2 placeholders will embed literal quotes in the generated .env file, breaking downstream parsing.


4-4: Make ENVIRONMENT configurable instead of hardcoded.

Hardcoding ENVIRONMENT="production" reduces deployment flexibility across different environments.


13-14: Parametrize database host and port for flexibility.

Hardcoded localhost and 5432 prevent deployment to external database configurations.


66-68: Construct SERVICE_ACCOUNT_FILE path dynamically.

The service account file path should include the full deployment directory path for consistency.

ansible/education-website.service.j2 (1)

5-11: Consider adding systemd security hardening options.

The service could benefit from additional security measures to limit potential attack surface.

ansible/update.sh.j2 (2)

8-8: Security risk: sudo usage in webhook context.

Using sudo in a script intended for Git webhooks creates significant security risks as noted in previous reviews.


3-8: Add error handling for critical operations.

The script lacks error handling for operations that could fail silently, making debugging difficult.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc23507 and 34edf51.

📒 Files selected for processing (7)
  • ansible/ansible.cfg (1 hunks)
  • ansible/education-website.service.j2 (1 hunks)
  • ansible/env.j2 (1 hunks)
  • ansible/nginx-http.conf.j2 (1 hunks)
  • ansible/nginx.conf.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • ansible/update.sh.j2 (1 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
ansible/playbook.yml

[warning] 21-21: truthy value should be one of [false, true]

(truthy)


[error] 117-117: too many spaces inside braces

(braces)


[warning] 117-117: truthy value should be one of [false, true]

(truthy)


[warning] 117-117: truthy value should be one of [false, true]

(truthy)


[error] 117-117: too many spaces inside braces

(braces)


[warning] 124-124: truthy value should be one of [false, true]

(truthy)


[warning] 131-131: truthy value should be one of [false, true]

(truthy)


[warning] 137-137: truthy value should be one of [false, true]

(truthy)


[warning] 157-157: truthy value should be one of [false, true]

(truthy)


[warning] 197-197: truthy value should be one of [false, true]

(truthy)


[warning] 199-199: truthy value should be one of [false, true]

(truthy)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run Tests
  • GitHub Check: build-and-test
🔇 Additional comments (7)
ansible/ansible.cfg (1)

1-8: Configuration looks good with security concern addressed.

The SSH host key checking is properly commented out to maintain secure defaults, addressing the previous security concern. The configuration settings are appropriate for Ansible deployment.

ansible/education-website.service.j2 (1)

6-10: Path corrections look good - service now runs with proper user permissions.

The working directory and executable paths have been correctly updated to use the VPS user's home directory instead of root, addressing the previous security concerns.

ansible/nginx-http.conf.j2 (2)

37-43: Static and media file paths correctly use non-root directory.

The file serving locations properly use /home/{{ vps_user }}/{{ project_name }}/ paths, avoiding the security and permission issues that would occur with /root/ paths.


25-52: Server configuration looks well-structured.

The server block handles domain names appropriately, includes ACME challenge location for SSL certificates, and properly proxies to the application server with correct headers.

ansible/update.sh.j2 (1)

2-2: Path correctly updated to use proper user directory.

The script now properly changes to /home/{{ vps_user }}/{{ project_name }} instead of the problematic /root/ path.

ansible/nginx.conf.j2 (2)

1-5: Global directives look good
User, worker processes, PID, and module includes follow standard best practices for an Nginx deployment.


57-63: Verify presence and permissions of static/media directories
You’re aliasing /home/{{ vps_user }}/{{ project_name }}/staticfiles/ and /media/. Confirm these paths are created and owned by Nginx in your playbook to avoid 403 errors.

Comment thread ansible/nginx.conf.j2
Comment thread ansible/nginx.conf.j2
Comment thread ansible/nginx.conf.j2
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (13)
ansible/env.j2 (5)

2-3: Remove quotes around Jinja2 placeholders to prevent parsing issues

The quotes around {{ secret_key }} and {{ debug }} will embed literal quotes in the generated .env file, potentially breaking downstream parsing.

Apply this fix:

-SECRET_KEY="{{ secret_key }}"
-DEBUG="{{ debug }}"
+SECRET_KEY={{ secret_key }}
+DEBUG={{ debug }}

4-4: Make ENVIRONMENT configurable instead of hardcoded

Hardcoding ENVIRONMENT="production" reduces deployment flexibility.

-ENVIRONMENT="production"
+ENVIRONMENT={{ environment | default('production') }}

6-7: Fix quoted placeholders for host configurations

Similar to the SECRET_KEY issue, these quoted placeholders will cause parsing problems.

-ALLOWED_HOSTS="{{ allowed_hosts }}"
-CSRF_TRUSTED_ORIGINS="{{ csrf_trusted_origins }}"
+ALLOWED_HOSTS={{ allowed_hosts }}
+CSRF_TRUSTED_ORIGINS={{ csrf_trusted_origins }}

13-14: Make database connection parameters configurable

Hardcoded database host and port limit deployment flexibility.

-DB_HOST="localhost"
-DB_PORT="5432"
+DB_HOST={{ db_host | default('localhost') }}
+DB_PORT={{ db_port | default('5432') }}

66-68: Construct SERVICE_ACCOUNT_FILE path dynamically

The service account file path should reflect the deployment directory structure.

{% if service_account_file %}
-SERVICE_ACCOUNT_FILE="{{ service_account_file }}"
+SERVICE_ACCOUNT_FILE="/home/{{ vps_user }}/{{ project_name }}/{{ service_account_file }}"
{% endif %}
ansible/playbook.yml (8)

1-4: Add privilege escalation to bootstrap play

The bootstrap play performs system-level operations requiring root privileges but lacks become: true.

- name: 0️⃣  Bootstrap OS
  hosts: all
+ become: true
  gather_facts: false

54-57: Add privilege escalation to deployment play

The deployment play also needs elevated privileges for system operations.

- name: 1️⃣  Deploy application
  hosts: all
+ become: true
  gather_facts: false

88-88: Remove insecure default credentials

Providing default values like 'change_me' for secret_key and db_password creates security risks if environment variables aren't set.

-        secret_key: "{{ env_vars.SECRET_KEY | default('change_me') }}"
+        secret_key: "{{ env_vars.SECRET_KEY }}"
-        db_password: "{{ env_vars.DB_PASSWORD | default('change_me') }}"
+        db_password: "{{ env_vars.DB_PASSWORD }}"

Also applies to: 110-110


109-109: Avoid defaulting db_user to root

Using root as the default database user violates the principle of least privilege.

-        db_user: "{{ env_vars.DB_USER | default('root') }}"
+        db_user: "{{ env_vars.DB_USER | default('ubuntu') }}"

63-82: Simplify .env parsing with read_env lookup plugin

The manual line-by-line parsing is error-prone and complex. Use the dedicated plugin instead.

-    - name: Read .env file into list
-      set_fact:
-        env_lines: "{{ lookup('ansible.builtin.file', '../.env').splitlines() }}"
-
-    - name: Init empty env_vars dict
-      set_fact:
-        env_vars: {}
-
-    - name: Build env_vars dict line-by-line
-      set_fact:
-        env_vars: "{{ env_vars | combine({ key: val }) }}"
-      vars:
-        clean: "{{ item.split('#',1)[0] | trim }}"
-        key: "{{ clean.split('=',1)[0] | trim }}"
-        val: "{{ (clean.split('=',1)[1] | trim) if ('=' in clean) else '' }}"
-      loop: "{{ env_lines }}"
-      when: clean != '' and not clean.startswith('#')
-      loop_control:
-        label: "{{ key }}"
+    - name: Read .env into env_vars
+      set_fact:
+        env_vars: "{{ lookup('community.general.read_env', '../.env') }}"

137-148: Replace shell commands with native Ansible modules

Using shell for venv creation and pip installation undermines idempotency.

-    - name: Create venv + install deps
-      become: yes
-      become_user: "{{ vps_user }}"
-      shell: |
-        python3.11 -m venv venv
-        source venv/bin/activate
-        pip install --upgrade pip wheel
-        pip install -r requirements.txt uvicorn gunicorn psycopg2-binary
-      args:
-        chdir: "{{ project_root }}"
-        executable: /bin/bash
+    - name: Create Python virtual environment
+      ansible.builtin.python3_virtualenv:
+        path: "{{ project_root }}/venv"
+        python: python3.11
+        clear: true
+      become_user: "{{ vps_user }}"
+
+    - name: Install Python dependencies
+      ansible.builtin.pip:
+        requirements: "{{ project_root }}/requirements.txt"
+        virtualenv: "{{ project_root }}/venv"
+        extra_args: "uvicorn gunicorn psycopg2-binary"
+      become_user: "{{ vps_user }}"

170-172: Add privilege escalation to hardening play

The hardening play requires root privileges for systemd and firewall operations.

- name: 2️⃣  Harden & run
  hosts: all
+ become: true
  gather_facts: false

180-184: Add daemon reload for systemd service updates

Systemd needs to reload its configuration when service files change.

    - name: Copy systemd service
      template:
        src: education-website.service.j2
        dest: /etc/systemd/system/education-website.service
+      register: systemd_service_updated

+    - name: Reload systemd daemon
+      systemd:
+        daemon_reload: true
+      when: systemd_service_updated is changed
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34edf51 and 25237be.

📒 Files selected for processing (3)
  • ansible/env.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • web/settings.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`web/settings.py`: The settings.py file is located in the web folder.

web/settings.py: The settings.py file is located in the web folder.

  • web/settings.py
🪛 YAMLlint (1.37.1)
ansible/playbook.yml

[warning] 21-21: truthy value should be one of [false, true]

(truthy)


[error] 118-118: too many spaces inside braces

(braces)


[warning] 118-118: truthy value should be one of [false, true]

(truthy)


[warning] 118-118: truthy value should be one of [false, true]

(truthy)


[error] 118-118: too many spaces inside braces

(braces)


[warning] 125-125: truthy value should be one of [false, true]

(truthy)


[warning] 132-132: truthy value should be one of [false, true]

(truthy)


[warning] 138-138: truthy value should be one of [false, true]

(truthy)


[warning] 158-158: truthy value should be one of [false, true]

(truthy)


[warning] 198-198: truthy value should be one of [false, true]

(truthy)


[warning] 200-200: truthy value should be one of [false, true]

(truthy)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run Tests
  • GitHub Check: build-and-test

Comment thread ansible/playbook.yml Outdated
Comment thread web/settings.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (7)
ansible/nginx.conf.j2 (2)

32-32: Use boolean filter for robust condition evaluation.

The string comparison enable_https_redirect == "True" is fragile. Use the Jinja |bool filter for proper boolean evaluation.

-    {% if enable_https_redirect == "True" and domain_name %}
+    {% if enable_https_redirect|bool and domain_name %}

93-93: Preserve original Host header for multi-domain flexibility.

Using the templated domain name {{ domain_name | default('alphaonelabs.com') }} limits flexibility. Consider using $host to preserve the original Host header from the client.

-            proxy_set_header Host {{ domain_name | default('alphaonelabs.com') }};
+            proxy_set_header Host $host;
ansible/playbook.yml (5)

104-105: Remove insecure defaults for sensitive credentials.

The default values 'root' for db_user and 'change_me' for db_password create security risks by allowing insecure deployments. Remove these defaults to make the variables mandatory.

-        db_user: "{{ env_vars.DB_USER | default('root') }}"
-        db_password: "{{ env_vars.DB_PASSWORD | default('change_me') }}"
+        db_user: "{{ env_vars.DB_USER }}"
+        db_password: "{{ env_vars.DB_PASSWORD }}"

49-51: Add privilege escalation to deployment play.

The "Deploy application" play lacks become: true, which is needed for user creation and file operations requiring elevated privileges.

- name: 1️⃣  Deploy application
  hosts: all
  gather_facts: false
+ become: true

165-167: Add privilege escalation to hardening play.

The "Harden & run" play requires become: true for template, systemd, and firewall operations that need root privileges.

- name: 2️⃣  Harden & run
  hosts: all
  gather_facts: false
+ become: true

113-113: Fix YAML formatting and boolean literals.

The user task has formatting issues and uses non-standard boolean values.

-    - name: Ensure application user exists
-      user: { name: "{{ vps_user }}", system: true, create_home: true, shell: /bin/bash }
+    - name: Ensure application user exists
+      user:
+        name: "{{ vps_user }}"
+        system: true
+        create_home: true
+        shell: /bin/bash

132-142: Replace shell commands with native Ansible modules.

Using shell for virtual environment and pip operations breaks idempotency. Use native Ansible modules for better reliability.

-    - name: Create venv + install deps
-      become: yes
-      become_user: "{{ vps_user }}"
-      shell: |
-        python3.11 -m venv venv
-        source venv/bin/activate
-        pip install --upgrade pip wheel
-        pip install -r requirements.txt uvicorn gunicorn psycopg2-binary
-      args:
-        chdir: "{{ project_root }}"
-        executable: /bin/bash
+    - name: Create Python virtual environment
+      ansible.builtin.python3_virtualenv:
+        path: "{{ project_root }}/venv"
+        python: python3.11
+        clear: true
+
+    - name: Install Python dependencies
+      ansible.builtin.pip:
+        requirements: "{{ project_root }}/requirements.txt"
+        virtualenv: "{{ project_root }}/venv"
+        extra_args: "uvicorn gunicorn psycopg2-binary"
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25237be and c4bd20f.

📒 Files selected for processing (3)
  • ansible/nginx.conf.j2 (1 hunks)
  • ansible/playbook.yml (1 hunks)
  • web/settings.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`web/settings.py`: The settings.py file is located in the web folder.

web/settings.py: The settings.py file is located in the web folder.

  • web/settings.py
🪛 YAMLlint (1.37.1)
ansible/playbook.yml

[warning] 22-22: truthy value should be one of [false, true]

(truthy)


[error] 113-113: too many spaces inside braces

(braces)


[error] 113-113: too many spaces inside braces

(braces)


[warning] 120-120: truthy value should be one of [false, true]

(truthy)


[warning] 127-127: truthy value should be one of [false, true]

(truthy)


[warning] 133-133: truthy value should be one of [false, true]

(truthy)


[warning] 153-153: truthy value should be one of [false, true]

(truthy)


[warning] 193-193: truthy value should be one of [false, true]

(truthy)


[warning] 195-195: truthy value should be one of [false, true]

(truthy)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build-and-test
  • GitHub Check: Run Tests
🔇 Additional comments (2)
web/settings.py (1)

76-87: Excellent implementation of environment-driven configuration!

The changes successfully address the previous feedback by moving from hardcoded values to environment variables using env.list(). The defaults are sensible (localhost for development) and the production domains remain configured. This aligns perfectly with the Ansible deployment setup where these values can be dynamically configured via the ansible/env.j2 template.

ansible/nginx.conf.j2 (1)

67-67: Verify SSE proxy configuration is correct.

The proxy_pass for SSE endpoints now correctly uses http://127.0.0.1:{{ app_port }}, which addresses the previous hardcoded socket issue. This looks good!

Comment thread ansible/playbook.yml
Comment thread ansible/playbook.yml
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 25, 2025
Comment thread web/settings.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 561f58e and 1515f68.

📒 Files selected for processing (1)
  • web/settings.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`web/settings.py`: The settings.py file is located in the web folder.

web/settings.py: The settings.py file is located in the web folder.

📄 Source: CodeRabbit Inference Engine (.cursorrules)

List of files the instruction was applied to:

  • web/settings.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: Abhishek-Punhani
PR: alphaonelabs/alphaonelabs-education-website#337
File: web/templates/courses/detail.html:1506-1560
Timestamp: 2025-04-07T11:33:41.277Z
Learning: For the alphaonelabs-education-website project, the team prefers to keep PRs focused and manageable in size. Additional enhancements like accessibility improvements should be addressed in separate follow-up issues rather than expanding the scope of an existing PR.
web/settings.py (1)
Learnt from: Inshamhaque
PR: alphaonelabs/alphaonelabs-education-website#0
File: :0-0
Timestamp: 2025-04-01T18:35:22.604Z
Learning: The DEBUG setting in Django's settings.py may not properly read from .env files if the environment configuration isn't correctly set up. For development environments, it's sometimes more reliable to explicitly set DEBUG = True in settings.py.
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Run Tests
  • GitHub Check: build-and-test
🔇 Additional comments (1)
web/settings.py (1)

76-86: LGTM! Environment-driven configuration addresses previous concerns.

The transition to using env.list() with sensible defaults for ALLOWED_HOSTS successfully addresses the previous feedback about hardcoded IP addresses while maintaining flexibility for different deployment environments.

Comment thread web/settings.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1515f68 and a3d7e87.

📒 Files selected for processing (1)
  • .env.sample (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: Abhishek-Punhani
PR: alphaonelabs/alphaonelabs-education-website#337
File: web/templates/courses/detail.html:1506-1560
Timestamp: 2025-04-07T11:33:41.277Z
Learning: For the alphaonelabs-education-website project, the team prefers to keep PRs focused and manageable in size. Additional enhancements like accessibility improvements should be addressed in separate follow-up issues rather than expanding the scope of an existing PR.
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
  • GitHub Check: build-and-test
  • GitHub Check: Linting
  • GitHub Check: Run Tests

Comment thread .env.sample
Comment thread .env.sample Outdated
Comment thread .env.sample

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3d7e87 and 54f7a1c.

📒 Files selected for processing (1)
  • web/settings.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`web/settings.py`: The settings.py file is located in the web folder.

web/settings.py: The settings.py file is located in the web folder.

📄 Source: CodeRabbit Inference Engine (.cursorrules)

List of files the instruction was applied to:

  • web/settings.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: Abhishek-Punhani
PR: alphaonelabs/alphaonelabs-education-website#337
File: web/templates/courses/detail.html:1506-1560
Timestamp: 2025-04-07T11:33:41.277Z
Learning: For the alphaonelabs-education-website project, the team prefers to keep PRs focused and manageable in size. Additional enhancements like accessibility improvements should be addressed in separate follow-up issues rather than expanding the scope of an existing PR.
web/settings.py (2)
Learnt from: Inshamhaque
PR: alphaonelabs/alphaonelabs-education-website#279
File: web/static/js/classes_map.js:0-0
Timestamp: 2025-03-24T20:02:40.021Z
Learning: In Django applications with i18n enabled, static assets referenced in JavaScript files should use absolute paths (starting with '/') rather than relative paths to prevent URL resolution issues when accessed from localized URLs (e.g., /en/some-page/).
Learnt from: Inshamhaque
PR: alphaonelabs/alphaonelabs-education-website#0
File: :0-0
Timestamp: 2025-04-01T18:35:22.604Z
Learning: The DEBUG setting in Django's settings.py may not properly read from .env files if the environment configuration isn't correctly set up. For development environments, it's sometimes more reliable to explicitly set DEBUG = True in settings.py.
🪛 Ruff (0.11.9)
web/settings.py

81-81: Possible binding to all interfaces

(S104)

🔇 Additional comments (1)
web/settings.py (1)

89-98: Excellent environment-driven configuration with comprehensive defaults.

The change to use env.list() for CSRF_TRUSTED_ORIGINS is well-implemented and includes appropriate defaults for both local development and production environments. This addresses previous feedback about including local development URLs and aligns perfectly with the Ansible deployment approach.

Comment thread web/settings.py
A1L13N
A1L13N previously approved these changes Jun 27, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
.env.sample (3)

32-32: Still missing a space after the # – please fix the header

The section header remains #Deployment Configuration; previous reviews already asked to change it to # Deployment Configuration for consistency and to satisfy linters that flag malformed comments.

-#Deployment Configuration
+# Deployment Configuration

33-40: Variable block is still out of alphabetical order

dotenv-linter will continue to emit UnorderedKey warnings because the keys are not sorted (e.g., VPS_USER precedes VPS_PASSWORD). Re-order them alphabetically to silence the linter and keep the file easy to scan.

-VPS_IP=your-vps-ip
-VPS_USER=root
-VPS_PASSWORD=your-vps-password
-PROJECT_NAME=education-website
-DOMAIN_NAME=yourdomain.com
-APP_PORT=8000
-PYTHON_VERSION=3.11
-ENABLE_HTTPS_REDIRECT=True
+APP_PORT=8000
+DOMAIN_NAME=yourdomain.com
+ENABLE_HTTPS_REDIRECT=True
+PROJECT_NAME=education-website
+PYTHON_VERSION=3.11
+VPS_IP=your-vps-ip
+VPS_PASSWORD=your-vps-password
+VPS_USER=root

41-43: DB_PASSWORD should come before DB_USER

dotenv-linter flags another UnorderedKey warning here. Swap the two lines:

-DB_NAME=education_website
-DB_USER=root
-DB_PASSWORD=your_secure_database_password
+DB_NAME=education_website
+DB_PASSWORD=your_secure_database_password
+DB_USER=root
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54f7a1c and 48c0edb.

📒 Files selected for processing (1)
  • .env.sample (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Abhishek-Punhani
PR: alphaonelabs/alphaonelabs-education-website#337
File: web/templates/courses/detail.html:1506-1560
Timestamp: 2025-04-07T11:33:41.277Z
Learning: For the alphaonelabs-education-website project, the team prefers to keep PRs focused and manageable in size. Additional enhancements like accessibility improvements should be addressed in separate follow-up issues rather than expanding the scope of an existing PR.
.env.sample (1)
Learnt from: prakharsingh-74
PR: alphaonelabs/alphaonelabs-education-website#0
File: :0-0
Timestamp: 2025-03-21T12:33:05.785Z
Learning: In Python code, comments should be indented to match the code block they describe. A comment with incorrect indentation will trigger a linting error (E111: indentation is not a multiple of 4) when using linters like flake8 or tools that enforce PEP 8 style guidelines.
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (python)
  • GitHub Check: build-and-test
  • GitHub Check: Run Tests
  • GitHub Check: Linting

Comment thread .env.sample
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 27, 2025
A1L13N
A1L13N previously approved these changes Jun 27, 2025
@A1L13N
A1L13N added this pull request to the merge queue Jun 27, 2025
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 27, 2025
@ishaan-arora-1
ishaan-arora-1 dismissed stale reviews from A1L13N and coderabbitai[bot] via fe695fa June 27, 2025 21:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
.github/workflows/test.yml (2)

59-61: Same observation as lines 20-22 – the extra mkdir is unnecessary and can be dropped or deduplicated via an anchor.


99-101: Ditto for the security job; see earlier comment regarding redundancy of this step.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 290a94b and fe695fa.

📒 Files selected for processing (1)
  • .github/workflows/test.yml (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: Abhishek-Punhani
PR: alphaonelabs/alphaonelabs-education-website#337
File: web/templates/courses/detail.html:1506-1560
Timestamp: 2025-04-07T11:33:41.277Z
Learning: For the alphaonelabs-education-website project, the team prefers to keep PRs focused and manageable in size. Additional enhancements like accessibility improvements should be addressed in separate follow-up issues rather than expanding the scope of an existing PR.
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build-and-test
  • GitHub Check: Run Tests

Comment thread .github/workflows/test.yml
@ishaan-arora-1
ishaan-arora-1 requested a review from A1L13N June 27, 2025 22:07
@A1L13N
A1L13N added this pull request to the merge queue Jun 27, 2025
Merged via the queue into alphaonelabs:main with commit 3636e19 Jun 27, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Jul 11, 2025
@ishaan-arora-1

Copy link
Copy Markdown
Contributor Author

@coderabbitai write on the implementation of this pr, and mention all tools use, why and how?

@coderabbitai

coderabbitai Bot commented Nov 10, 2025

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@ishaan-arora-1: Here's a comprehensive breakdown of the implementation in this PR:

Overview

This PR implements an automated deployment infrastructure using Infrastructure as Code (IaC) principles. It enables one-command deployment of the Django education website to a VPS server, handling everything from OS configuration to SSL certificate management.

Tools Used and Their Purposes

1. Ansible (Configuration Management & Orchestration)

Why: Automates server provisioning, application deployment, and configuration management in a declarative, idempotent manner.

How it's used:

  • ansible.cfg: Configures Ansible behavior (inventory location, SSH pipelining, Python interpreter)
  • playbook.yml: Defines three deployment stages:
    • Stage 0 (Bootstrap OS): Installs system dependencies (Python 3.11, PostgreSQL, Nginx, Git), creates swap space, performs system reboot
    • Stage 1 (Deploy application): Creates app user, clones repository, sets up virtual environment, installs dependencies, generates .env file, runs migrations, collects static files
    • Stage 2 (Harden & run): Configures Nginx, sets up systemd service, configures UFW firewall (ports 22, 80, 443)

2. Nginx (Web Server & Reverse Proxy)

Why: Serves static files efficiently and proxies application requests to Gunicorn.

How it's used:

  • nginx-http.conf.j2: Basic HTTP configuration template
  • nginx.conf.j2: Full configuration with conditional HTTPS redirect support
  • Serves static/media files directly
  • Proxies dynamic requests to Gunicorn on configured app_port
  • Special SSE (Server-Sent Events) handling for admin log streaming with disabled buffering and long timeouts
  • Handles Certbot ACME challenges for SSL certificate validation

3. Gunicorn + Uvicorn Workers (Application Server)

Why: Production-grade WSGI/ASGI server for running Django applications with async support.

How it's used:

  • Configured in education-website.service.j2 systemd unit
  • Binds to 127.0.0.1:{{ app_port }} (localhost only, exposed via Nginx)
  • Uses Uvicorn workers for ASGI support (WebSockets, async views)

4. Systemd (Service Management)

Why: Ensures the application runs as a system service with automatic restart on failure.

How it's used:

  • education-website.service.j2: Template defining the service unit
  • Runs Gunicorn under specified user/group
  • Auto-restarts on failure
  • Starts after network is available
  • Managed via systemctl commands

5. PostgreSQL (Database)

Why: Production-ready relational database for Django.

How it's used:

  • Installed via Ansible in bootstrap stage
  • Connection details configured in env.j2 template
  • Database credentials passed via environment variables

6. UFW (Uncomplicated Firewall) (Security)

Why: Restricts network access to essential ports only.

How it's used:

  • Opens only ports 22 (SSH), 80 (HTTP), 443 (HTTPS)
  • Blocks all other incoming connections
  • Configured and enabled in playbook's hardening stage

7. Git (Version Control)

Why: Pulls application code from repository during deployment and updates.

How it's used:

  • Clones repository in deployment stage
  • update.sh.j2 template pulls latest changes during updates

8. Python Virtual Environment (Dependency Isolation)

Why: Isolates project dependencies from system Python packages.

How it's used:

  • Created in /home/{{ vps_user }}/{{ project_name }}/venv
  • All Python operations (pip install, Django commands) run within venv
  • Referenced in systemd service and update script

9. Jinja2 Templates (Configuration Templating)

Why: Enables dynamic configuration based on environment variables.

How templates are used:

  • env.j2: Generates .env file with database credentials, secrets, API keys
  • nginx.conf.j2 / nginx-http.conf.j2: Generates Nginx config with domain, ports
  • education-website.service.j2: Generates systemd unit file
  • update.sh.j2: Generates update script for the deployed environment

10. Bash Scripts (Automation)

Why: Provide convenient entry points for deployment operations.

How they're used:

  • deploy.sh: Loads .env, validates VPS credentials, executes Ansible playbook
  • update.sh.j2: Template for post-deployment updates (git pull, migrations, static files, service restart)

Deployment Workflow

graph TD
    A["Developer runs deploy.sh"] --> B["Load .env variables"]
    B --> C["Validate VPS_IP"]
    C --> D["Execute Ansible Playbook"]
    D --> E["Stage 0: Bootstrap OS"]
    E --> F["Install packages"]
    F --> G["Create swap"]
    G --> H["Reboot server"]
    H --> I["Stage 1: Deploy App"]
    I --> J["Create user &amp; clone repo"]
    J --> K["Setup venv &amp; install deps"]
    K --> L["Generate .env from template"]
    L --> M["Run migrations &amp; collectstatic"]
    M --> N["Stage 2: Harden &amp; Run"]
    N --> O["Configure Nginx"]
    O --> P["Setup systemd service"]
    P --> Q["Configure UFW firewall"]
    Q --> R["Start services"]
    R --> S["Deployment Complete"]
Loading

Key Configuration Changes

.env.sample

Added deployment-specific variables:

  • VPS connection details (VPS_IP, VPS_USER, VPS_PASSWORD)
  • Database configuration
  • Domain and HTTPS settings
  • Git deployment source
  • Security settings (ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS)

web/settings.py

Changed from hardcoded lists to environment-driven configuration:

ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[...])
CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", default=[...])

This allows different configurations per environment without code changes.

Security Features

  1. UFW firewall limiting exposed ports
  2. No host key checking disabled in Ansible (secure by default)
  3. Environment-based secrets (not hardcoded)
  4. SSH pipelining for faster, more secure connections
  5. HTTPS support with Certbot integration
  6. Application running as non-root user
  7. Localhost-only Gunicorn binding (exposed only via Nginx)

How to Use

  1. Initial deployment:

    cd ansible
    ./deploy.sh
  2. Updates (on VPS):

    /home/<user>/<project>/update.sh

This implementation provides a production-ready, repeatable, and maintainable deployment solution that follows DevOps best practices.


🧠 Learnings used
Learnt from: Abhishek-Punhani
Repo: alphaonelabs/alphaonelabs-education-website PR: 337
File: web/templates/courses/detail.html:1506-1560
Timestamp: 2025-04-07T11:33:41.277Z
Learning: For the alphaonelabs-education-website project, the team prefers to keep PRs focused and manageable in size. Additional enhancements like accessibility improvements should be addressed in separate follow-up issues rather than expanding the scope of an existing PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ansible

2 participants