1
0
Fork 0
forked from OpenNeo/impress
impress/deploy/setup.yml

478 lines
14 KiB
YAML
Raw Permalink Normal View History

---
- name: Set up the environment for the impress app
hosts: webserver
become: yes
become_user: root
vars:
email_address: "emi@matchu.dev" # TODO: Extract this to personal config?
impress_hostname: impress.openneo.net
vars_files:
# mysql_root_password, mysql_user_password, mysql_user_password_2020,
# dev_ips
- files/setup_secrets.yml
tasks:
- name: Set hostname to impress.openneo.net
hostname:
name: impress.openneo.net
- name: Create SSH folder for logged-in user
become: no
file:
name: .ssh
mode: "700"
state: directory
- name: Copy authorized SSH keys to logged-in user
become: no
copy:
dest: ~/.ssh/authorized_keys
src: files/authorized-ssh-keys.txt
mode: "600"
- name: Disable root SSH login
lineinfile:
dest: /etc/ssh/sshd_config
regexp: ^#?PermitRootLogin
line: PermitRootLogin no
- name: Disable password-based SSH authentication
lineinfile:
dest: /etc/ssh/sshd_config
regexp: ^#?PasswordAuthentication
line: PasswordAuthentication no
- name: Enable public-key SSH authentication
lineinfile:
dest: /etc/ssh/sshd_config
regexp: ^#?PubkeyAuthentication
line: PubkeyAuthentication yes
- name: Update the apt cache
apt:
update_cache: yes
- name: Install fail2ban firewall with default settings
apt:
name: fail2ban
- name: Configure ufw firewall to allow SSH connections on port 22
community.general.ufw:
rule: allow
port: "22"
- name: Configure ufw firewall to allow HTTP connections on port 80
community.general.ufw:
rule: allow
port: "80"
- name: Configure ufw firewall to allow HTTPS connections on port 443
community.general.ufw:
rule: allow
port: "443"
- name: Configure ufw firewall to allow MySQL connections from impress-2020
community.general.ufw:
rule: allow
port: "3306"
from_ip: "{{ item }}"
loop:
- "45.56.112.222"
- "2600:3c02::f03c:92ff:fe9a:4615"
- name: Configure ufw firewall to allow MySQL connections from known devs
community.general.ufw:
rule: allow
port: "3306"
from_ip: "{{ item }}"
loop: "{{ dev_ips }}"
- name: Enable ufw firewall with all other ports closed by default
community.general.ufw:
state: enabled
policy: deny
- name: Install unattended-upgrades
apt:
name: unattended-upgrades
- name: Enable unattended-upgrades to auto-upgrade our system
copy:
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
dest: /etc/apt/apt.conf.d/20auto-upgrades
- name: Configure unattended-upgrades to auto-reboot our server when necessary
lineinfile:
regex: ^(//\s*)?Unattended-Upgrade::Automatic-Reboot ".*";$
line: Unattended-Upgrade::Automatic-Reboot "true";
dest: /etc/apt/apt.conf.d/50unattended-upgrades
- name: Configure unattended-upgrades to delay necessary reboots to 3am
lineinfile:
regex: ^(//\s*)?Unattended-Upgrade::Automatic-Reboot-Time ".*";$
line: Unattended-Upgrade::Automatic-Reboot-Time "03:00";
dest: /etc/apt/apt.conf.d/50unattended-upgrades
- name: Configure the system timezone to be US Pacific time
community.general.timezone:
name: America/Los_Angeles
- name: Create "impress" user
user:
name: impress
comment: Impress App
home: /srv/impress
create_home: false
shell: /bin/bash
- name: Create "impress-deployers" group
group:
name: impress-deployers
- name: Add the current user to the "impress-deployers" group
user:
name: "{{ lookup('env', 'USER') }}"
groups:
- impress-deployers
append: yes
# We use this so the deploy playbook doesn't have to prompt for a root
# password: this user just is trusted to act as "impress" in the future.
- name: Enable the "impress-deployers" group to freely act as the "impress" user
community.general.sudoers:
name: impress-deployers-as-impress
group: impress-deployers
runas: impress
commands: ALL
nopassword: yes
# Similarly, this enables us to manage the impress service in the deploy playbook
# and in live debugging without a password.
# NOTE: In the sudoers file, you need to specify the full path to the
# command, to avoid tricks where you use PATH to get around the intent!
- name: Enable the "impress-deployers" group to freely start and stop the impress service
community.general.sudoers:
name: impress-deployers-systemctl
group: impress-deployers
commands:
- /bin/systemctl status impress
- /bin/systemctl start impress
- /bin/systemctl stop impress
- /bin/systemctl restart impress
nopassword: yes
- name: Install ACL, to enable us to run commands as the "impress" user
apt:
name: acl
- name: Install ruby-build
git:
repo: https://github.com/rbenv/ruby-build.git
dest: /opt/ruby-build
version: e1b36a32fb87d61955ac38f1889b7e3cb3b2f407
- name: Check if Ruby 3.3.0 is already installed
stat:
path: /opt/ruby-3.3.0
register: ruby_dir
- name: Install Ruby 3.3.0
command: "/opt/ruby-build/bin/ruby-build 3.3.0 /opt/ruby-3.3.0"
when: not ruby_dir.stat.exists
- name: Add Ruby 3.3.0 to the global PATH, for developer convenience
copy:
dest: /etc/profile.d/ruby_path.sh
content: PATH="/opt/ruby-3.3.0/bin:$PATH"
- name: Install system dependencies for impress's Ruby gems
apt:
name:
- libmysqlclient-dev
- libyaml-dev
- name: Create the app folder
file:
path: /srv/impress
owner: impress
group: impress
mode: "755"
state: directory
- name: Add a convenient .bash_profile for when we log in as "impress"
copy:
owner: impress
group: impress
dest: /srv/impress/.bash_profile
content: |
set -a # Export all of the below
RAILS_ENV=production
EXECJS_RUNTIME=Disabled
source /srv/impress/shared/production.env
set +a
- name: Create the app's "versions" folder
become_user: impress
file:
path: /srv/impress/versions
state: directory
- name: Create the app's "shared" folder
become_user: impress
file:
path: /srv/impress/shared
state: directory
- name: Check for a current app version
stat:
path: /srv/impress/current
register: current_app_version
- name: Check whether we already have a placeholder app
stat:
path: /srv/impress/versions/initial-placeholder
register: existing_placeholder_app
when: not current_app_version.stat.exists
- name: Create a placeholder app, to run until we deploy a real version
become_user: impress
copy:
src: files/initial-placeholder
dest: /srv/impress/versions
when: |
not current_app_version.stat.exists and
not existing_placeholder_app.stat.exists
- name: Configure the placeholder app to run in deployment mode
become_user: impress
command:
chdir: /srv/impress/versions/initial-placeholder
cmd: /opt/ruby-3.3.0/bin/bundle config set --local deployment true
when: not current_app_version.stat.exists
- name: Install the placeholder app's dependencies
become_user: impress
command:
chdir: /srv/impress/versions/initial-placeholder
cmd: /opt/ruby-3.3.0/bin/bundle install
when: not current_app_version.stat.exists
- name: Set the placeholder app as the current version
become_user: impress
file:
src: /srv/impress/versions/initial-placeholder
dest: /srv/impress/current
state: link
when: not current_app_version.stat.exists
# NOTE: This file is uploaded with stricter permissions, to help protect
# the secrets inside. Most of the app is world-readable for convenience
# for debugging and letting nginx serve static files, but keep this safer!
- name: Upload the production.env file
become_user: impress
copy:
dest: /srv/impress/shared/production.env
src: files/production.env
mode: "600"
notify:
- Reload systemctl
- Restart impress
Create `rails public_data:commit` task, to share public data dumps I'm starting to port over the functionality that was previously just, me running `yarn db:export:public-data` in `impress-2020` and committing it to Git LFS every time. My immediate motivation is that the `impress-2020` git repository is getting weirdly large?? Idk how these 40MB files have blown up to a solid 16GB of Git LFS data (we don't have THAT many!!!), but I guess there's something about Git LFS's architecture and disk usage that I'm not understanding. So, let's move to a simpler system in which we don't bind the public data to the codebase, but instead just regularly dump it in production and make it available for download. This change adds the `rails public_data:commit` task, which when run in production will make the latest available at `https://impress.openneo.net/public-data/latest.sql.gz`, and will also store a running log of previous dumps, viewable at `https://impress.openneo.net/public-data/`. Things left to do: 1. Create a `rails public_data:pull` task, to download `latest.sql.gz` and import it into the local development database. 2. Set up a cron job to dump this out regularly, idk maybe weekly? That will grow, but not very fast (about 2GB per year), and we can add logic to rotate out old ones if it starts to grow too far. (If we wanted to get really intricate, we could do like, daily for the past week, then weekly for the past 3 months, then monthly for the past year, idk. There must be tools that do this!)
2024-02-29 14:30:33 -08:00
- name: Create the shared public-data folder
become_user: impress
file:
dest: /srv/impress/shared/public-data
mode: "755"
state: directory
- name: Create service file for impress
copy:
src: files/impress.service
dest: /etc/systemd/system/impress.service
notify:
- Reload systemctl
- Restart impress
- name: Configure impress to run now, and automatically when the system starts
systemd:
name: impress
state: started
enabled: true
- name: Install nginx
apt:
name: nginx
- name: Install core snap
community.general.snap:
name: core
- name: Install certbot as a snap
community.general.snap:
name: certbot
classic: yes
- name: Set up certbot
command: "certbot certonly --nginx -n --agree-tos --email {{ email_address }} --domains {{ impress_hostname }}"
- name: Add impress config file to nginx
template:
src: files/sites-available/impress.conf
dest: /etc/nginx/sites-available/impress.conf
notify:
- Reload nginx
- name: Enable impress config file in nginx
file:
src: /etc/nginx/sites-available/impress.conf
dest: /etc/nginx/sites-enabled/impress.conf
state: link
notify:
- Reload nginx
- name: Add openneo-home config file to nginx
template:
src: files/sites-available/openneo-home.conf
dest: /etc/nginx/sites-available/openneo-home.conf
notify:
- Reload nginx
- name: Enable openneo-home config file in nginx
file:
src: /etc/nginx/sites-available/openneo-home.conf
dest: /etc/nginx/sites-enabled/openneo-home.conf
state: link
notify:
- Reload nginx
- name: Install MariaDB
apt:
name: mariadb-server
- name: Install a Python MySQL client, for Ansible to use when configuring
apt:
name: python3-mysqldb
- name: Update MariaDB root password
community.mysql.mysql_user:
name: root
host_all: true
password: "{{mysql_root_password}}"
- name: Create root's .my.cnf file
copy:
content: |
[client]
user=root
password='{{ mysql_root_password }}'
dest: /root/.my.cnf
mode: 0400
- name: Remove test database
community.mysql.mysql_db:
name: test
state: absent
login_unix_socket: "{{ login_unix_socket | default(omit) }}"
- name: Remove anonymous users
community.mysql.mysql_user:
name: ""
state: absent
host_all: true
- name: Remove remote root access
community.mysql.mysql_query:
query:
- DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')
- name: Expose MariaDB to the internet (but ufw will block most clients)
copy:
dest: /etc/mysql/mariadb.conf.d/80-bind-address.cnf
content: |
[mysqld]
skip-networking=0
skip-bind-address
notify: Restart MariaDB
# This is the best Unicode collation available in our version of MariaDB!
# We already specify it for all the tables in `schema.rb`, but also set it
# as the default collation for new tables here, too.
- name: Set MariaDb's default collation to utf8mb4_unicode_520_ci
copy:
dest: /etc/mysql/mariadb.conf.d/80-charsets.cnf
content: |
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_520_ci
notify: Restart MariaDB
- name: Enable slow query logging for MariaDB
copy:
dest: /etc/mysql/mariadb.conf.d/80-logging.cnf
content: |
[mysqld]
slow-query-log
notify: Restart MariaDB
- name: Create MySQL databases
community.mysql.mysql_db:
name:
- openneo_impress
- openneo_id
- name: Create MySQL user openneo_impress
community.mysql.mysql_user:
name: openneo_impress
password: "{{ mysql_user_password }}"
priv: "openneo_impress.*:ALL,openneo_id.*:ALL"
- name: Create MySQL user impress2020
community.mysql.mysql_user:
name: impress2020
password: "{{ mysql_user_password_2020 }}"
priv: "openneo_impress.*:ALL,openneo_id.*:ALL"
Create NeopetsMediaArchive, read the actual manifests for Alt Styles The Neopets Media Archive is a service that mirrors `images.neopets.com` over time! Right now we're starting by just loading manifests, and using them to replace the hacks we used for determining the Alt Style PNG and SVG URLs; but with time, I want to load *all* customization media files, to have our own secondary file source that isn't dependent on Neopets to always be up. Impress 2020 already caches manifest files, but this strategy is different in two ways: 1. We're using the filesystem rather than a database column. (That is, manifest data is kinda duplicated in the system right now!) This is because I intend to go in a more file-y way long-term anyway, to load more than just the manifests. 2. Impress 2020 guesses at the manifest URLs by pattern, and reloads them on a regular basis. Instead, we use the modeling system: when TNT changes the URL of a manifest by appending a new `?v=` query string to it, this system will consider it a new URL, and will load the new copy accordingly. Fun fact, I actually have been prototyping some of this stuff in a side project I'd named `impress-media-server`! It's a little Sinatra app that indeed *does* save all the files needed for customization, and can generate lightweight lil preview iframes and images pretty easily. I had initially been planning this as a separate service, but after thinking over the arch a bit, I think it'll go smoother to just give the main app all the same access and awareness—and I wrote it all in Ruby and plain HTML/JS/CSS, so it should be pretty easy to port over bit-by-bit! Anyway, only Alt Styles use this for now, but my motivation is to be able to use more-correct asset URL logic to be able to finally swap over wardrobe-2020's item search to impress.openneo.net's item search API endpoint—which will get "Items You Own" searches working again, and whittle down one of the last big things Impress 2020 can do that the main app can't. Let's see how it goes!
2024-02-23 12:02:39 -08:00
- name: Create the Neopets Media Archive data directory
file:
path: /var/lib/neopets-media-archive
owner: impress
group: impress
mode: "755"
state: directory
- name: Create 10min cron job to run `rails nc_mall:sync`
become_user: impress
cron:
name: "Impress: sync NC Mall data"
minute: "*/10"
job: "bash -c 'source /etc/profile && source ~/.bash_profile && cd /srv/impress/current && bin/rails nc_mall:sync'"
- name: Create weekly cron job to run `rails public_data:commit`
become_user: impress
cron:
name: "Impress: commit public data"
weekday: "0" # Sunday
hour: "1" # 1:15am
minute: "15" # 1:15am
job: "bash -c 'source /etc/profile && source ~/.bash_profile && cd /srv/impress/current && bin/rails public_data:commit[scheduled]'"
handlers:
- name: Reload nginx
systemd:
name: nginx
state: reloaded
- name: Restart MariaDB
systemd:
name: mariadb
state: restarted
- name: Reload systemctl
command: systemctl daemon-reload
- name: Restart impress
systemd:
name: impress
state: restarted