forked from OpenNeo/impress
Add playbook to deploy new app version
Okay, this is much simpler than the impress-2020 version where we symlinked node_modules and stuff - Bundler is just a lot better at this lol Right now, the app is failing to start because we don't install Node—I wasn't sure whether we'd need to and whether I was gonna precompile the assets etc Though now that I say that out loud, I guess part of the issue might be that I'm not sure the app is running in RAILS_ENV=production, I wonder if it still wants Node in that case?? I'll flip that switch in the service file now, then commit to save my place for the day, then try again with starting the app sometime and see what it says!
This commit is contained in:
parent
3dd5d26332
commit
c2abc8d876
3 changed files with 154 additions and 4 deletions
99
deploy/deploy.yml
Normal file
99
deploy/deploy.yml
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
---
|
||||||
|
- name: Deploy impress from the current local version
|
||||||
|
hosts: webserver
|
||||||
|
become: yes
|
||||||
|
become_user: impress
|
||||||
|
vars:
|
||||||
|
local_app_root: "{{ playbook_dir }}/.."
|
||||||
|
remote_project_root: "/srv/impress"
|
||||||
|
# deploy:setup should have added us to the "impress-deployers" group, so we
|
||||||
|
# should be able to become the "impress" user without a password.
|
||||||
|
ansible_become_password: ""
|
||||||
|
tasks:
|
||||||
|
- name: Generate a version name from the current timestamp
|
||||||
|
command: date '+%Y-%m-%d-%s'
|
||||||
|
register: new_app_version
|
||||||
|
|
||||||
|
- name: Print out the new version name
|
||||||
|
debug:
|
||||||
|
msg: "Deploying new version: {{ new_app_version.stdout }}"
|
||||||
|
|
||||||
|
- name: Save new remote folder path to a variable
|
||||||
|
set_fact:
|
||||||
|
remote_app_root: "{{ remote_project_root }}/versions/{{ new_app_version.stdout }}"
|
||||||
|
|
||||||
|
- name: Create new remote folder for the new version
|
||||||
|
file:
|
||||||
|
path: "{{ remote_app_root }}"
|
||||||
|
state: directory
|
||||||
|
|
||||||
|
- name: Copy local app's source files to new remote folder
|
||||||
|
ansible.posix.synchronize:
|
||||||
|
src: "{{ local_app_root }}/"
|
||||||
|
dest: "{{ remote_app_root }}"
|
||||||
|
rsync_opts:
|
||||||
|
- "--exclude=.git"
|
||||||
|
- "--filter=':- .gitignore'"
|
||||||
|
|
||||||
|
- name: Configure Bundler to run in deployment mode
|
||||||
|
command:
|
||||||
|
chdir: "{{ remote_app_root }}"
|
||||||
|
cmd: /opt/ruby-3.1.4/bin/bundle config set --local deployment true
|
||||||
|
|
||||||
|
# This ensures that, while attempting our current deploy, we don't
|
||||||
|
# accidentally delete gems out from under the currently-running version.
|
||||||
|
# NOTE: From reading the docs, I thiink this is the default behavior, but
|
||||||
|
# I can't be sure? Rather than deep-dive to find out, I'd rather just set
|
||||||
|
# it, to be clear about the default(?) behavior we're depending on.
|
||||||
|
- name: Configure Bundler to *not* clean up old gems when installing
|
||||||
|
command:
|
||||||
|
chdir: "{{ remote_app_root }}"
|
||||||
|
cmd: /opt/ruby-3.1.4/bin/bundle config set --local clean false
|
||||||
|
|
||||||
|
# NOTE: Bundler recommends this, and they're pretty smart about it: if the
|
||||||
|
# Gemfile changes, this shouldn't disrupt the currently-running version,
|
||||||
|
# because we won't clean up its now-unused gems yet, and if we upgrade a
|
||||||
|
# gem it'll install *both* versions of the gem until we clean up.
|
||||||
|
- name: Configure Bundler to use the bundle folder shared by all app versions
|
||||||
|
command:
|
||||||
|
chdir: "{{ remote_app_root }}"
|
||||||
|
cmd: "/opt/ruby-3.1.4/bin/bundle config set --local path {{ remote_project_root}}/shared/bundle"
|
||||||
|
|
||||||
|
- name: Run `bundle install` to install dependencies in remote folder
|
||||||
|
command:
|
||||||
|
chdir: "{{ remote_app_root }}"
|
||||||
|
cmd: /opt/ruby-3.1.4/bin/bundle install
|
||||||
|
|
||||||
|
- name: Update the `current` folder to point to the new version
|
||||||
|
file:
|
||||||
|
src: "{{ remote_app_root }}"
|
||||||
|
dest: /srv/impress/current
|
||||||
|
state: link
|
||||||
|
|
||||||
|
# NOTE: This uses the passwordless sudo rule we set up in deploy:setup.
|
||||||
|
# We write it as a command rather than using the built-in `systemd` Ansible
|
||||||
|
# module, to make sure we're invoking it exactly as we wrote in that rule.
|
||||||
|
# TODO: I'm not sure why it works to write `sudo` in the command instead of
|
||||||
|
# `become_user: root`, which complains about the missing sudo password, which
|
||||||
|
# we already fixed for the rest of the playbook I thought?
|
||||||
|
- name: Restart the app
|
||||||
|
become: no
|
||||||
|
command: sudo systemctl restart impress
|
||||||
|
|
||||||
|
- name: Clean up gems no longer used in the current app version
|
||||||
|
command:
|
||||||
|
chdir: "{{ remote_app_root }}"
|
||||||
|
cmd: /opt/ruby-3.1.4/bin/bundle clean
|
||||||
|
|
||||||
|
- name: Find older app versions to clean up
|
||||||
|
# Print out all but the 5 last-recently-updated versions.
|
||||||
|
command:
|
||||||
|
chdir: "{{ remote_project_root }}/versions"
|
||||||
|
cmd: bash -c 'ls -t | tail -n +6'
|
||||||
|
register: versions_to_clean_up
|
||||||
|
|
||||||
|
- name: Clean up older versions
|
||||||
|
file:
|
||||||
|
path: "{{ remote_project_root }}/versions/{{ item }}"
|
||||||
|
state: absent
|
||||||
|
with_items: "{{ versions_to_clean_up.stdout_lines }}"
|
|
@ -100,6 +100,42 @@
|
||||||
comment: Impress App
|
comment: Impress App
|
||||||
create_home: false
|
create_home: false
|
||||||
|
|
||||||
|
- 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
|
- name: Install ACL, to enable us to run commands as the "impress" user
|
||||||
apt:
|
apt:
|
||||||
name: acl
|
name: acl
|
||||||
|
@ -124,6 +160,10 @@
|
||||||
dest: /etc/profile
|
dest: /etc/profile
|
||||||
line: 'PATH="/opt/ruby-3.1.4/bin:$PATH" # Added by impress deploy setup script'
|
line: 'PATH="/opt/ruby-3.1.4/bin:$PATH" # Added by impress deploy setup script'
|
||||||
|
|
||||||
|
- name: Install system dependencies for impress's Ruby gems
|
||||||
|
apt:
|
||||||
|
name: libmysqlclient-dev
|
||||||
|
|
||||||
- name: Create the app folder
|
- name: Create the app folder
|
||||||
file:
|
file:
|
||||||
path: /srv/impress
|
path: /srv/impress
|
||||||
|
@ -132,12 +172,18 @@
|
||||||
mode: "755"
|
mode: "755"
|
||||||
state: directory
|
state: directory
|
||||||
|
|
||||||
- name: Create the app versions folder
|
- name: Create the app's "versions" folder
|
||||||
become_user: impress
|
become_user: impress
|
||||||
file:
|
file:
|
||||||
path: /srv/impress/versions
|
path: /srv/impress/versions
|
||||||
state: directory
|
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
|
- name: Check for a current app version
|
||||||
stat:
|
stat:
|
||||||
path: /srv/impress/current
|
path: /srv/impress/current
|
||||||
|
@ -186,9 +232,12 @@
|
||||||
- name: Upload the production.env file
|
- name: Upload the production.env file
|
||||||
become_user: impress
|
become_user: impress
|
||||||
copy:
|
copy:
|
||||||
dest: /srv/impress/production.env
|
dest: /srv/impress/shared/production.env
|
||||||
src: files/production.env
|
src: files/production.env
|
||||||
mode: "600"
|
mode: "600"
|
||||||
|
notify:
|
||||||
|
- Reload systemctl
|
||||||
|
- Restart impress
|
||||||
|
|
||||||
- name: Create service file for impress
|
- name: Create service file for impress
|
||||||
copy:
|
copy:
|
||||||
|
@ -202,7 +251,8 @@
|
||||||
Restart=always
|
Restart=always
|
||||||
WorkingDirectory=/srv/impress/current
|
WorkingDirectory=/srv/impress/current
|
||||||
ExecStart=/opt/ruby-3.1.4/bin/bundle exec puma --port=3000
|
ExecStart=/opt/ruby-3.1.4/bin/bundle exec puma --port=3000
|
||||||
EnvironmentFile=/srv/impress/production.env
|
Environment="RAILS_ENV=production"
|
||||||
|
EnvironmentFile=/srv/impress/shared/production.env
|
||||||
|
|
||||||
; Some security directives, adapted from Akkoma's service file, they seem like sensible defaults!
|
; Some security directives, adapted from Akkoma's service file, they seem like sensible defaults!
|
||||||
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx --loader:.png=file --loader:.svg=file --loader:.min.js=text",
|
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx --loader:.png=file --loader:.svg=file --loader:.min.js=text",
|
||||||
"build:production": "yarn build --minify",
|
"build:production": "yarn build --minify",
|
||||||
"deploy:setup": "echo $'Setup requires you to become the root user. You\\'ll need to enter the password for your account on the remote web server below, and you must be part of the `sudo` user group.' && ansible-playbook -K -i deploy/inventory.cfg deploy/setup.yml"
|
"deploy:setup": "echo $'Setup requires you to become the root user. You\\'ll need to enter the password for your account on the remote web server below, and you must be part of the `sudo` user group.' && ansible-playbook -K -i deploy/inventory.cfg deploy/setup.yml",
|
||||||
|
"deploy": "ansible-playbook -i deploy/inventory.cfg deploy/deploy.yml"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue