-
-
Couldn't load subscription status.
- Fork 240
ansible setup #4598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
ansible setup #4598
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Ansible Deployment | ||
|
|
||
| Minimal Ansible playbook to deploy the BLT Django project. | ||
|
|
||
| ## Files | ||
| - `inventory.yml` - Define your server host/IP and variables. | ||
| - `playbook.yml` - Executes deployment steps (clone repo, install deps, migrate, collectstatic, configure systemd + nginx). | ||
|
|
||
| ## Usage | ||
| 1. Edit `inventory.yml` and set: | ||
| - `ansible_host` | ||
| - `ansible_user` | ||
| - `domain` (optional) | ||
| 2. Run: | ||
| ```bash | ||
| ansible-playbook -i ansible/inventory.yml ansible/playbook.yml | ||
| ``` | ||
|
|
||
| ## Notes | ||
| - Installs dependencies using Poetry export to a requirements.txt installed into a virtualenv. | ||
| - Creates a systemd service `gunicorn-blt`. | ||
| - Nginx reverse proxies to Gunicorn on port 8000. | ||
| - Opens ports 22, 80, 443 with UFW. | ||
|
Comment on lines
+21
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Align README with the actual service (Uvicorn, not Gunicorn). The playbook provisions a systemd unit named 🤖 Prompt for AI Agents |
||
| - For HTTPS, you can manually install Certbot or extend the playbook. | ||
|
|
||
| ## Quick Certbot (optional) | ||
| ```bash | ||
| sudo apt install -y certbot python3-certbot-nginx | ||
| sudo certbot --nginx -d your.domain --email [email protected] --agree-tos --redirect | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,271 @@ | ||||||||||||||||||||||
| --- | ||||||||||||||||||||||
| - name: Deploy BLT Django App | ||||||||||||||||||||||
| hosts: blt_server | ||||||||||||||||||||||
| become: true | ||||||||||||||||||||||
| vars: | ||||||||||||||||||||||
| python_version: "3.11" | ||||||||||||||||||||||
| env_file: .env.production | ||||||||||||||||||||||
| service_name: blt-uvicorn | ||||||||||||||||||||||
| django_settings: blt.settings | ||||||||||||||||||||||
| listen_interface: 127.0.0.1 | ||||||||||||||||||||||
| enable_remote_postgres: "{{ enable_remote_postgres | default(true) }}" | ||||||||||||||||||||||
| pre_tasks: | ||||||||||||||||||||||
|
Comment on lines
+11
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lock down PostgreSQL remote access by default.
Please make remote access opt‑in (default - enable_remote_postgres: "{{ enable_remote_postgres | default(true) }}"
+ enable_remote_postgres: "{{ enable_remote_postgres | default(false) }}"
...
- - name: Open firewall ports
+ - name: Open firewall ports
ufw:
rule: allow
port: "{{ item }}"
loop:
- '22'
- '80'
- '443'
- - '5432'
+ - name: Allow PostgreSQL port when remote access enabled
+ ufw:
+ rule: allow
+ port: '5432'
+ when: enable_remote_postgres | boolThat keeps the database private unless the operator explicitly opts in. Also applies to: 157-171, 236-244 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| - name: Update apt cache | ||||||||||||||||||||||
| apt: | ||||||||||||||||||||||
| update_cache: yes | ||||||||||||||||||||||
| cache_valid_time: 3600 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Ensure system packages (app + PostgreSQL) | ||||||||||||||||||||||
| apt: | ||||||||||||||||||||||
| name: | ||||||||||||||||||||||
| - git | ||||||||||||||||||||||
| - python3-pip | ||||||||||||||||||||||
| - python3-venv | ||||||||||||||||||||||
| - build-essential | ||||||||||||||||||||||
| - libpq-dev | ||||||||||||||||||||||
| - python3-psycopg2 | ||||||||||||||||||||||
| - postgresql | ||||||||||||||||||||||
| - postgresql-contrib | ||||||||||||||||||||||
| - nginx | ||||||||||||||||||||||
| - ufw | ||||||||||||||||||||||
| - curl | ||||||||||||||||||||||
| state: present | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Add app user with home | ||||||||||||||||||||||
| user: | ||||||||||||||||||||||
| name: "{{ app_user }}" | ||||||||||||||||||||||
| system: yes | ||||||||||||||||||||||
| create_home: yes | ||||||||||||||||||||||
| shell: /bin/bash | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| tasks: | ||||||||||||||||||||||
| - name: Create app directory structure | ||||||||||||||||||||||
| file: | ||||||||||||||||||||||
| path: "{{ item }}" | ||||||||||||||||||||||
| state: directory | ||||||||||||||||||||||
| owner: "{{ app_user }}" | ||||||||||||||||||||||
| group: "{{ app_user }}" | ||||||||||||||||||||||
| mode: '0755' | ||||||||||||||||||||||
| loop: | ||||||||||||||||||||||
| - "{{ app_dir }}" | ||||||||||||||||||||||
| - "{{ app_dir }}/shared" | ||||||||||||||||||||||
| - "{{ app_dir }}/shared/logs" | ||||||||||||||||||||||
| - "{{ app_dir }}/shared/media" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Clone or update repository | ||||||||||||||||||||||
| git: | ||||||||||||||||||||||
| repo: "{{ repo_url }}" | ||||||||||||||||||||||
| dest: "{{ app_dir }}/current" | ||||||||||||||||||||||
| version: "{{ branch }}" | ||||||||||||||||||||||
| force: yes | ||||||||||||||||||||||
| register: git_result | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Ensure virtualenv | ||||||||||||||||||||||
| command: python3 -m venv {{ venv_dir }} | ||||||||||||||||||||||
| args: | ||||||||||||||||||||||
| creates: "{{ venv_dir }}/bin/activate" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Upgrade pip/setuptools/wheel | ||||||||||||||||||||||
| pip: | ||||||||||||||||||||||
| name: | ||||||||||||||||||||||
| - pip | ||||||||||||||||||||||
| - setuptools | ||||||||||||||||||||||
| - wheel | ||||||||||||||||||||||
| state: latest | ||||||||||||||||||||||
| virtualenv: "{{ venv_dir }}" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Install Python dependencies (poetry export) | ||||||||||||||||||||||
| shell: | | ||||||||||||||||||||||
| if command -v poetry >/dev/null 2>&1; then | ||||||||||||||||||||||
| cd {{ app_dir }}/current && poetry export -f requirements.txt --without-hashes -o /tmp/requirements.txt | ||||||||||||||||||||||
| else | ||||||||||||||||||||||
| cd {{ app_dir }}/current && pip install poetry && poetry export -f requirements.txt --without-hashes -o /tmp/requirements.txt | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
| {{ venv_dir }}/bin/pip install -r /tmp/requirements.txt | ||||||||||||||||||||||
| args: | ||||||||||||||||||||||
| executable: /bin/bash | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Create production env file (.env.production) | ||||||||||||||||||||||
| copy: | ||||||||||||||||||||||
| dest: "{{ app_dir }}/current/{{ env_file }}" | ||||||||||||||||||||||
| owner: "{{ app_user }}" | ||||||||||||||||||||||
| group: "{{ app_user }}" | ||||||||||||||||||||||
| mode: '0600' | ||||||||||||||||||||||
| content: | | ||||||||||||||||||||||
| DEBUG=False | ||||||||||||||||||||||
| ALLOWED_HOSTS=.{{ domain }},{{ domain }},127.0.0.1,localhost | ||||||||||||||||||||||
| SECRET_KEY={{ lookup('password', '/dev/null', length=64, chars='ascii_letters,digits') }} | ||||||||||||||||||||||
|
||||||||||||||||||||||
| SECRET_KEY={{ lookup('password', '/dev/null', length=64, chars='ascii_letters,digits') }} | |
| SECRET_KEY={{ lookup('password', '{{ app_dir }}/shared/secret_key', length=64, chars='ascii_letters,digits') }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Persist the Django SECRET_KEY instead of regenerating every run.
Using lookup('password', '/dev/null', ...) creates a brand-new secret on every playbook execution. That forces a service restart each time and invalidates all Django sessions/tokens, even when nothing else changed.
Please persist the generated value (e.g., write it once to a file under {{ app_dir }}/shared/secret_key and reuse it on subsequent runs) so reruns stay idempotent. Example:
- SECRET_KEY={{ lookup('password', '/dev/null', length=64, chars='ascii_letters,digits') }}
+ SECRET_KEY={{ lookup('password', '{{ app_dir }}/shared/secret_key', length=64, chars='ascii_letters,digits') }}This keeps the secret stable across deployments while still generating it on first run.
🤖 Prompt for AI Agents
In ansible/playbook.yml around lines 88-104, the SECRET_KEY is being regenerated
on every run using lookup('password', '/dev/null', ...), causing unnecessary
restarts and session invalidation; change the play so it first checks for a
persisted secret at {{ app_dir }}/shared/secret_key, if missing generate a new
secret and write it to that file with owner {{ app_user }} and mode 0600, then
read the secret from that file and insert its value into the .env.production
content (instead of calling lookup each run); ensure the task is idempotent
(only writes when file absent or empty) and that file permissions are secure so
subsequent playbook runs reuse the same SECRET_KEY.
Copilot
AI
Oct 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PostgreSQL password is exposed in the command line and process list. Use the postgresql_user Ansible module instead of shell commands to securely create database users without exposing credentials.
| become_user: postgres | |
| shell: | | |
| psql -tc "SELECT 1 FROM pg_roles WHERE rolname='{{ postgres_db_user }}'" | grep -q 1 || psql -c "CREATE ROLE {{ postgres_db_user }} LOGIN PASSWORD '{{ postgres_db_password }}';" | |
| changed_when: false | |
| community.postgresql.postgresql_user: | |
| name: "{{ postgres_db_user }}" | |
| password: "{{ postgres_db_password }}" | |
| login: true | |
| state: present | |
| become_user: postgres |
Copilot
AI
Oct 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PostgreSQL port 5432 is opened in the firewall unconditionally, but remote PostgreSQL access is controlled by the 'enable_remote_postgres' variable. The firewall rule should only open port 5432 when remote access is actually enabled to reduce attack surface.
| loop: | |
| - '22' | |
| - '80' | |
| - '443' | |
| - '5432' | |
| loop: "{{ ['22', '80', '443'] + (enable_remote_postgres | bool | ternary(['5432'], [])) }}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation incorrectly references 'gunicorn-blt' service and Gunicorn server, but the playbook actually creates a 'blt-uvicorn' service using uvicorn ASGI server. Update documentation to match the actual implementation.