wip: add ansible playbook and inventory for VPS provisioning
This commit is contained in:
8
deploy/inventory.ini
Normal file
8
deploy/inventory.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
# The Door — VPS Inventory
|
||||
# The crisis front door server
|
||||
|
||||
[the_door]
|
||||
# Production VPS — alexanderwhitestone.com
|
||||
# Update ansible_host if IP changes
|
||||
# ansible_user should be a sudo-capable user (not root recommended)
|
||||
67.205.155.108 ansible_user=root ansible_python_interpreter=/usr/bin/python3
|
||||
294
deploy/playbook.yml
Normal file
294
deploy/playbook.yml
Normal file
@@ -0,0 +1,294 @@
|
||||
---
|
||||
# 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
|
||||
Reference in New Issue
Block a user