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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions beoftexas/docker-compose-demo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: beoftexas
services:
nginx:
image: nginx:alpine
networks:
shared:
aliases:
- beoftexas-web
default:
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- public_files:/var/www/html/public:ro
depends_on:
- web
restart: always

web:
image: casmith/beoftexas:2026.01.21.30d8b812
env_file:
- .env
networks:
default:
environment:
DB_NAME: beoftexas
DB_HOST: mariadb
DB_USER: beoftexas
STRAPI_URL: https://demo-strapi.beoftexas.com
volumes:
- public_files:/var/www/html/public
depends_on:
- mariadb
restart: always

mariadb:
image: mariadb
restart: always
env_file:
- .env
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: beoftexas
MYSQL_USER: beoftexas
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- ./data/mariadb/data:/var/lib/mysql

volumes:
public_files:

networks:
shared:
external: true
55 changes: 55 additions & 0 deletions beoftexas/docker/nginx-demo/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
server {
listen 8000;
server_name _;
root /var/www/html/public;
index index.php index.html;

# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ajax.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://demo-strapi.beoftexas.com https://ajax.googleapis.com;" always;

# Serve static files directly
# Note: add_header in location blocks overrides parent headers, so we repeat security headers here
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
try_files $uri =404;
}

# Main location block
location / {
try_files $uri $uri/ /index.php?$query_string;
}

# PHP-FPM proxy
location ~ \.php$ {
fastcgi_pass web:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;

# FastCGI settings
fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
}

# Deny access to hidden files
location ~ /\. {
deny all;
}
}
2 changes: 1 addition & 1 deletion beoftexas/docker/nginx/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ server {
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://strapi.beoftexas.com;" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ajax.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://strapi.beoftexas.com https://ajax.googleapis.com;" always;

# Serve static files directly
# Note: add_header in location blocks overrides parent headers, so we repeat security headers here
Expand Down
89 changes: 89 additions & 0 deletions beoftexas/playbook-demo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
- name: Deploy Benefit Elect of Texas website via Docker (Demo)
hosts: demo
become: true
tasks:
- name: Log into DockerHub
docker_login:
username: '{{ lookup("env", "DOCKERHUB_USERNAME") }}'
password: '{{ lookup("env", "DOCKERHUB_PASSWORD") }}'

- name: Creates directory
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: ubuntu
group: ubuntu
loop:
- "{{ deploy_path }}"
- "{{ deploy_path }}/beoftexas"
- "{{ deploy_path }}/beoftexas/data"
- "{{ deploy_path }}/beoftexas/docker/nginx"

- name: Create shared Docker network
community.docker.docker_network:
name: shared
state: present

- name: copy Docker Compose files
copy:
src: "docker-compose-demo.yaml"
dest: "{{ deploy_path }}/beoftexas/docker-compose.yaml"
owner: ubuntu
group: ubuntu

- name: copy nginx config (demo version with updated CSP)
copy:
src: "docker/nginx-demo/default.conf"
dest: "{{ deploy_path }}/beoftexas/docker/nginx/default.conf"
owner: ubuntu
group: ubuntu

- name: copy environment file
copy:
src: "../secret/demo/beoftexas.env"
dest: "{{ deploy_path }}/beoftexas/.env"
mode: '0600'
owner: ubuntu
group: ubuntu

- name: Deploy with docker compose
ansible.builtin.shell:
cmd: "docker compose up -d"
chdir: "{{ deploy_path }}/beoftexas/"
register: compose_up_result

- name: Gracefully reload beoftexas nginx config
ansible.builtin.shell:
cmd: "docker compose exec -T nginx nginx -s reload"
chdir: "{{ deploy_path }}/beoftexas/"
register: nginx_reload_result
failed_when: false

- name: Wait for PHP-FPM container to be ready
ansible.builtin.shell:
cmd: "docker compose exec -T web php -v"
chdir: "{{ deploy_path }}/beoftexas/"
register: php_check
retries: 10
delay: 3
until: php_check.rc == 0

- name: Wait for MariaDB to be ready
ansible.builtin.shell:
cmd: "docker compose exec -T mariadb mariadb -u beoftexas -p$(grep '^DB_PASSWORD=' .env | cut -d'=' -f2-) -e 'SELECT 1' 2>&1"
chdir: "{{ deploy_path }}/beoftexas/"
register: db_check
retries: 30
delay: 2
until: db_check.rc == 0

# NOTE: Migrations are run after database restore in deploy-demo.sh
# Do not run migrations here on empty database

- name: Restart reverse proxy nginx to pick up beoftexas on shared network
ansible.builtin.shell:
cmd: "docker compose restart web"
chdir: "{{ deploy_path }}/nginx-demo"
register: nginx_restart_result
failed_when: false
changed_when: nginx_restart_result.rc == 0
174 changes: 174 additions & 0 deletions beoftexas/restore-db-demo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
- name: Force restore beoftexas database from backup (Demo)
hosts: demo
gather_facts: false
become: true
vars:
deploy_path: /home/ubuntu/deploy/beoftexas
s3_access_key: "{{ lookup('env', 'CONTABO_S3_ACCESS_KEY') }}"
s3_secret_key: "{{ lookup('env', 'CONTABO_S3_SECRET_KEY') }}"
s3_bucket: "{{ lookup('env', 'CONTABO_S3_BUCKET') | default('beoftexas-backup', true) }}"
env_file: "{{ deploy_path }}/.env"

tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600

- name: Install unzip utility
ansible.builtin.apt:
name: unzip
state: present

- name: Check if AWS CLI is already installed
ansible.builtin.command: aws --version
register: aws_cli_check
changed_when: false
failed_when: false

- name: Download AWS CLI v2 installer
ansible.builtin.get_url:
url: https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip
dest: /tmp/awscliv2.zip
mode: '0644'
when: aws_cli_check.rc != 0

- name: Unzip AWS CLI installer
ansible.builtin.unarchive:
src: /tmp/awscliv2.zip
dest: /tmp/
remote_src: true
when: aws_cli_check.rc != 0

- name: Install AWS CLI v2
ansible.builtin.command: /tmp/aws/install
when: aws_cli_check.rc != 0

- name: Clean up AWS CLI installer files
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /tmp/awscliv2.zip
- /tmp/aws
when: aws_cli_check.rc != 0

- name: Download database backup from Contabo S3
ansible.builtin.shell: |
AWS_ACCESS_KEY_ID="{{ s3_access_key }}" \
AWS_SECRET_ACCESS_KEY="{{ s3_secret_key }}" \
aws s3 cp s3://{{ s3_bucket }}/beoftexas_latest.sql.gz /tmp/beoftexas_latest.sql.gz \
--endpoint-url https://usc1.contabostorage.com
no_log: false
changed_when: true

- name: Verify backup file was downloaded
ansible.builtin.stat:
path: /tmp/beoftexas_latest.sql.gz
register: backup_file
failed_when: not backup_file.stat.exists or backup_file.stat.size == 0

- name: Display backup file info
ansible.builtin.debug:
msg: "Backup file downloaded: {{ backup_file.stat.size }} bytes"

- name: Stop beoftexas application service
ansible.builtin.shell: |
cd {{ deploy_path }} && \
docker compose stop web
register: stop_result
changed_when: true

- name: Display stop result
ansible.builtin.debug:
msg: "Beoftexas service stopped"

- name: Wait for MariaDB to be ready
ansible.builtin.shell: |
cd {{ deploy_path }} && \
docker compose exec -T mariadb mariadb -u root -p$(grep "^DB_ROOT_PASSWORD=" {{ env_file }} | cut -d'=' -f2-) -e "SELECT 1" 2>&1
register: db_ready
until: db_ready.rc == 0
retries: 30
delay: 2
changed_when: false
no_log: false

- name: Drop existing beoftexas database
ansible.builtin.shell: |
cd {{ deploy_path }} && \
docker compose exec -T mariadb mariadb -u root -p$(grep "^DB_ROOT_PASSWORD=" {{ env_file }} | cut -d'=' -f2-) -e "DROP DATABASE IF EXISTS beoftexas;"
register: drop_result
changed_when: true
no_log: true

- name: Display database drop status
ansible.builtin.debug:
msg: "Database dropped successfully"

- name: Create fresh beoftexas database
ansible.builtin.shell: |
cd {{ deploy_path }} && \
docker compose exec -T mariadb mariadb -u root -p$(grep "^DB_ROOT_PASSWORD=" {{ env_file }} | cut -d'=' -f2-) -e "CREATE DATABASE beoftexas CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
register: create_result
changed_when: true
no_log: true

- name: Display database creation status
ansible.builtin.debug:
msg: "Database created successfully"

- name: Test backup file integrity
ansible.builtin.shell: gunzip -t /tmp/beoftexas_latest.sql.gz
register: integrity_check
changed_when: false
failed_when: integrity_check.rc != 0

- name: Restore database from backup
ansible.builtin.shell: |
cd {{ deploy_path }} && \
gunzip -c /tmp/beoftexas_latest.sql.gz | docker compose exec -T mariadb mariadb -u root -p$(grep "^DB_ROOT_PASSWORD=" {{ env_file }} | cut -d'=' -f2-) beoftexas
register: restore_result
changed_when: true
no_log: false

- name: Display restore completion
ansible.builtin.debug:
msg: "Database restoration completed"

- name: Verify database has tables after restore
ansible.builtin.shell: |
cd {{ deploy_path }} && \
docker compose exec -T mariadb mariadb -u root -p$(grep "^DB_ROOT_PASSWORD=" {{ env_file }} | cut -d'=' -f2-) beoftexas -e "SHOW TABLES;" 2>/dev/null | wc -l
register: table_count
changed_when: false
no_log: true
failed_when: (table_count.stdout | int) <= 1

- name: Display table count
ansible.builtin.debug:
msg: "Database restored successfully with {{ (table_count.stdout | int) - 1 }} tables"

- name: Restart beoftexas application service
ansible.builtin.shell: |
cd {{ deploy_path }} && \
docker compose start web
register: start_result
changed_when: true

- name: Display restart status
ansible.builtin.debug:
msg: "Beoftexas service restarted successfully"

- name: Clean up temporary backup file
ansible.builtin.file:
path: /tmp/beoftexas_latest.sql.gz
state: absent

- name: Display final status
ansible.builtin.debug:
msg:
- "====================================="
- "Database restore completed successfully!"
- "====================================="
14 changes: 14 additions & 0 deletions cloudflared/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: cloudflared
services:
tunnel:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
networks:
- shared
restart: always

networks:
shared:
external: true
Loading