Files
the-door/deploy/playbook.yml
2026-04-13 04:11:42 +00:00

295 lines
7.8 KiB
YAML

---
# The Door — Ansible Playbook
# VPS provisioning for the crisis front door
#
# Usage:
# cd deploy && ansible-playbook -i inventory.ini playbook.yml
#
# This playbook is IDEMPOTENT — safe to run repeatedly.
# It handles: swap, nginx, SSL, firewall, site deployment.
- name: "The Door — VPS Provisioning"
hosts: the_door
become: true
vars:
domain: "alexanderwhitestone.com"
domain_www: "www.alexanderwhitestone.com"
site_root: "/var/www/the-door"
swap_size: "2G"
swap_file: "/swapfile"
hermes_port: 8644
deploy_dir: "/opt/the-door"
tasks:
# ================================================================
# PHASE 1: System — swap, updates, packages
# ================================================================
- name: "[swap] Check if swapfile exists"
stat:
path: "{{ swap_file }}"
register: swap_stat
- name: "[swap] Create swapfile"
command: fallocate -l {{ swap_size }} {{ swap_file }}
when: not swap_stat.stat.exists
- name: "[swap] Set permissions"
file:
path: "{{ swap_file }}"
mode: "0600"
when: not swap_stat.stat.exists
- name: "[swap] Make swap"
command: mkswap {{ swap_file }}
when: not swap_stat.stat.exists
- name: "[swap] Enable swap"
command: swapon {{ swap_file }}
when: not swap_stat.stat.exists
- name: "[swap] Add to fstab"
lineinfile:
path: /etc/fstab
line: "{{ swap_file }} none swap sw 0 0"
state: present
when: not swap_stat.stat.exists
- name: "[apt] Update cache"
apt:
update_cache: yes
cache_valid_time: 3600
- name: "[apt] Install packages"
apt:
name:
- nginx
- certbot
- python3-certbot-nginx
- ufw
- curl
state: present
# ================================================================
# PHASE 2: Site files — copy static assets
# ================================================================
- name: "[site] Create webroot"
file:
path: "{{ site_root }}"
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: "[site] Copy index.html"
copy:
src: "{{ playbook_dir }}/../index.html"
dest: "{{ site_root }}/index.html"
owner: www-data
group: www-data
mode: "0644"
notify: reload nginx
- name: "[site] Copy manifest.json"
copy:
src: "{{ playbook_dir }}/../manifest.json"
dest: "{{ site_root }}/manifest.json"
owner: www-data
group: www-data
mode: "0644"
notify: reload nginx
- name: "[site] Copy service worker"
copy:
src: "{{ playbook_dir }}/../sw.js"
dest: "{{ site_root }}/sw.js"
owner: www-data
group: www-data
mode: "0644"
notify: reload nginx
- name: "[site] Copy system prompt"
copy:
src: "{{ playbook_dir }}/../system-prompt.txt"
dest: "{{ site_root }}/system-prompt.txt"
owner: www-data
group: www-data
mode: "0644"
- name: "[site] Copy about page"
copy:
src: "{{ playbook_dir }}/../about.html"
dest: "{{ site_root }}/about.html"
owner: www-data
group: www-data
mode: "0644"
notify: reload nginx
- name: "[site] Copy testimony page"
copy:
src: "{{ playbook_dir }}/../testimony.html"
dest: "{{ site_root }}/testimony.html"
owner: www-data
group: www-data
mode: "0644"
notify: reload nginx
# ================================================================
# PHASE 3: nginx — config, sites, rate limiting
# ================================================================
- name: "[nginx] Ensure sites-available dir"
file:
path: /etc/nginx/sites-available
state: directory
- name: "[nginx] Ensure sites-enabled dir"
file:
path: /etc/nginx/sites-enabled
state: directory
- name: "[nginx] Deploy site config"
copy:
src: "{{ playbook_dir }}/nginx.conf"
dest: /etc/nginx/sites-available/the-door
owner: root
group: root
mode: "0644"
notify: reload nginx
- name: "[nginx] Enable site"
file:
src: /etc/nginx/sites-available/the-door
dest: /etc/nginx/sites-enabled/the-door
state: link
notify: reload nginx
- name: "[nginx] Remove default site"
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: reload nginx
- name: "[nginx] Add rate limit zone to main config"
lineinfile:
path: /etc/nginx/nginx.conf
insertafter: "http {"
line: " limit_req_zone $binary_remote_addr zone=the_door_api:10m rate=10r/m;"
notify: reload nginx
- name: "[nginx] Test config"
command: nginx -t
changed_when: false
- name: "[nginx] Ensure service is running"
service:
name: nginx
state: started
enabled: yes
# ================================================================
# PHASE 4: Firewall — UFW
# ================================================================
- name: "[ufw] Allow SSH"
ufw:
rule: allow
port: "22"
proto: tcp
- name: "[ufw] Allow HTTP"
ufw:
rule: allow
port: "80"
proto: tcp
- name: "[ufw] Allow HTTPS"
ufw:
rule: allow
port: "443"
proto: tcp
- name: "[ufw] Set default deny incoming"
ufw:
direction: incoming
policy: deny
- name: "[ufw] Set default allow outgoing"
ufw:
direction: outgoing
policy: allow
- name: "[ufw] Enable firewall"
ufw:
state: enabled
# ================================================================
# PHASE 5: SSL — certbot (manual trigger recommended)
# ================================================================
- name: "[ssl] Check if cert exists"
stat:
path: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"
register: ssl_cert
- name: "[ssl] Obtain certificate (if DNS is pointed)"
command: >
certbot --nginx
-d {{ domain }}
-d {{ domain_www }}
--non-interactive
--agree-tos
--register-unsafely-without-email
when: not ssl_cert.stat.exists
register: certbot_result
ignore_errors: true
- name: "[ssl] Certbot result"
debug:
msg: "{{ 'SSL cert obtained' if certbot_result.rc == 0 else 'SSL cert needs manual setup — point DNS first, then run: certbot --nginx -d ' + domain + ' -d ' + domain_www }}"
when: not ssl_cert.stat.exists
# ================================================================
# PHASE 6: Deploy directory + deploy script
# ================================================================
- name: "[deploy] Create deploy directory"
file:
path: "{{ deploy_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: "[deploy] Copy deploy script"
copy:
src: "{{ playbook_dir }}/deploy.sh"
dest: "{{ deploy_dir }}/deploy.sh"
owner: root
group: root
mode: "0755"
- name: "[deploy] Copy system-prompt.txt"
copy:
src: "{{ playbook_dir }}/../system-prompt.txt"
dest: "{{ deploy_dir }}/system-prompt.txt"
owner: root
group: root
mode: "0644"
# ================================================================
# HANDLERS
# ================================================================
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
- name: restart nginx
service:
name: nginx
state: restarted