├── .gitignore ├── README.md ├── Vagrantfile ├── ansible.cfg ├── app-vars.yml ├── deploy.yml ├── deploy_tasks └── after_cleanup.yml ├── group_vars └── all │ └── vault.yml ├── images └── ansible-rails-promo.jpg ├── inventories └── development.ini ├── provision.yml ├── roles ├── certbot │ ├── defaults │ │ └── main.yml │ └── tasks │ │ ├── cert.yml │ │ └── main.yml ├── common │ ├── defaults │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── fluentbit │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── td-agent-bit.conf.j2 ├── logrotate │ ├── defaults │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── nginx │ ├── defaults │ │ └── main.yml │ ├── files │ │ ├── config │ │ │ ├── general.conf │ │ │ ├── letsencrypt.conf │ │ │ ├── proxy.conf │ │ │ └── security.conf │ │ └── nginx.conf │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── nginx-default.conf.j2 ├── nodejs │ ├── defaults │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── pgbackup │ ├── defaults │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── postgresql-backup.j2 ├── postgresql │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── pg_hba.conf.j2 │ │ └── postgresql.conf.j2 ├── puma │ └── tasks │ │ └── main.yml ├── redis │ └── tasks │ │ └── main.yml ├── ruby │ ├── defaults │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── sidekiq │ └── tasks │ │ └── main.yml ├── ssh │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── ufw │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── user │ ├── defaults │ │ └── main.yml │ └── tasks │ │ └── main.yml └── yarn │ └── tasks │ └── main.yml └── templates ├── .rbenv-vars.j2 ├── database.yml.j2 ├── nginx.conf.j2 ├── puma.service.j2 └── sidekiq.service.j2 /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vault_pass 3 | /tmp/ 4 | .vagrant/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 |
6 | Ansible Rails is a playbook for easily deploying Ruby on Rails applications. It uses Vagrant to provision an environment where you can test your deploys. [Ansistrano](https://github.com/ansistrano/deploy) is used for finally deploying our app to staging and production environments.
7 |
8 | While this is meant to work out of the box, you can tweak the files in the `roles` directory in order to satisfy your project-specific requirements.
9 |
10 | > **Shameless plug:** If you're looking for a simple bookmarking tool, try [EmailThis.me](https://www.emailthis.me) - a simpler alternative to Pocket that helps you *save ad-free articles and web pages to your email inbox*.
11 |
12 | ---
13 |
14 | ### What does this do?
15 | * Configure our server with some sensible defaults
16 | * Install required/useful packages. See notes below for more details.
17 | * Auto upgrade all installed packages (TODO)
18 | * Create a new deployment user (called 'deploy') with passwordless login
19 | * SSH hardening
20 | * Prevent password login
21 | * Change the default SSH port
22 | * Prevent root login
23 | * Setup UFW (firewall)
24 | * Setup Fail2ban
25 | * Install Logrotate
26 | * Setup Nginx with some sensible config (thanks to nginxconfig.io)
27 | * Certbot (for Let's encrypt SSL certificates)
28 | * Ruby (using Rbenv).
29 | * Defaults to `2.6.6`. You can change it in the `app-vars.yml` file
30 | * [jemmaloc](https://github.com/jemalloc/jemalloc) is also installed and configured by default
31 | * [rbenv-vars](https://github.com/rbenv/rbenv-vars) is also installed by default
32 | * Node.js
33 | * Defaults to 12.x. You can change it in the `app-vars.yml` file.
34 | * Yarn
35 | * Redis (latest)
36 | * Postgresql.
37 | * Defaults to v12. You can specify the version that you need in the `app-vars.yml` file.
38 | * Puma (with Systemd support for restarting automatically) **See Puma Config section below**
39 | * Sidekiq (with Systemd support for restarting automatically)
40 | * Ansistrano hooks for performing the following tasks -
41 | * Installing all our gems
42 | * Precompiling assets
43 | * Migrating our database (using `run_once`)
44 |
45 | ---
46 |
47 | ### Getting started
48 | Here are the steps that you need to follow in order to get up and running with Ansible Rails.
49 |
50 | #### Step 1. Installation
51 |
52 | ```
53 | git clone https://github.com/EmailThis/ansible-rails ansible-rails
54 | cd ansible-rails
55 | ```
56 |
57 | #### Step 2. Configuration
58 | Open `app-vars.yml` and change the following variables. Additionally, please review the `app-vars.yml` and see if there is anything else that you would like to modify (e.g.: install some other packages, change ruby, node or postgresql versions etc.)
59 |
60 | ```
61 | app_name: YOUR_APP_NAME // Replace with name of your app
62 | app_git_repo: "YOUR_GIT_REPO" // e.g.: github.com/EmailThis/et
63 | app_git_branch: "master" // branch that you want to deploy (e.g: 'production')
64 |
65 | postgresql_db_user: "{{ deploy_user }}_postgresql_user"
66 | postgresql_db_password: "{{ vault_postgresql_db_password }}" # from vault (see next section)
67 | postgresql_db_name: "{{ app_name }}_production"
68 |
69 | nginx_https_enabled: false # change to true if you wish to install SSL certificate
70 | ```
71 |
72 |
73 | #### Step 3. Storing sensitive information
74 | Create a new `vault` file to store sensitive information
75 | ```
76 | ansible-vault create group_vars/all/vault.yml
77 | ```
78 |
79 | Add the following information to this new vault file
80 | ```
81 | vault_postgresql_db_password: "XXXXX_SUPER_SECURE_PASS_XXXXX"
82 | vault_rails_master_key: "XXXXX_MASTER_KEY_FOR_RAILS_XXXXX"
83 | ```
84 |
85 | #### Step 4. Deploy
86 |
87 | Now that we have configured everything, lets see if everything is working locally. Run the following command -
88 | ```
89 | vagrant up
90 | ```
91 | Now open your browser and navigate to 192.168.50.2. You should see your Rails application.
92 |
93 | If you don't wish to use Vagrant, clone this repo, modify the `inventories/development.ini` file to suit your needs, and then run the following command
94 | ```
95 | ansible-playbook -i inventories/development.ini provision.yml
96 | ```
97 |
98 | To deploy this app to your production server, create another file inside `inventories` directory called `production.ini` with the following contents. For this, you would need a VPS. I've used [DigitalOcean](https://m.do.co/c/031c76b9c838) and [Vultr](https://www.vultr.com/?ref=8597223) in production for my apps and both these services are top-notch.
99 | ```
100 | [web]
101 | 192.168.50.2 # replace with IP address of your server.
102 |
103 | [all:vars]
104 | ansible_ssh_user=deployer
105 | ansible_python_interpreter=/usr/bin/python3
106 | ```
107 |
108 | ---
109 |
110 | ### Additional Configuration
111 |
112 | #### Installing additional packages
113 | By default, the following packages are installed. You can add/remove packages to this list by changing the `required_package` variable in `app-vars.yml`
114 | ```
115 | - curl
116 | - ufw
117 | - fail2ban
118 | - git-core
119 | - apt-transport-https
120 | - ca-certificates
121 | - software-properties-common
122 | - python3-pip
123 | - virtualenv
124 | - python3-setuptools
125 | - zlib1g-dev
126 | - build-essential
127 | - libssl-dev
128 | - libreadline-dev
129 | - libyaml-dev
130 | - libxml2-dev
131 | - libxslt1-dev
132 | - libcurl4-openssl-dev
133 | - libffi-dev
134 | - dirmngr
135 | - gnupg
136 | - autoconf
137 | - bison
138 | - libreadline6-dev
139 | - libncurses5-dev
140 | - libgdbm5
141 | - libgdbm-dev
142 | - libpq-dev # postgresql client
143 | - libjemalloc-dev # jemalloc
144 | ```
145 |
146 | #### Enable UFW
147 | You can enable UFW by adding the role to `provision.yml` like so -
148 | ```
149 | roles:
150 | ...
151 | ...
152 | - role: ufw
153 | tags: ufw
154 | ```
155 |
156 | Then you can set up the UFW rules in `app-vars.yml` like so -
157 | ```
158 | ufw_rules:
159 | - { rule: "allow", proto: "tcp", from: "any", port: "80" }
160 | - { rule: "allow", proto: "tcp", from: "any", port: "443" }
161 | ```
162 |
163 | #### Enable Certbot (Let's Encrypt SSL certificates)
164 |
165 | Add the role to `provision.yml`
166 | ```
167 | roles:
168 | ...
169 | ...
170 | - role: certbot
171 | tags: certbot
172 | ```
173 |
174 | Add the following variables to `app-vars.yml`
175 | ```
176 | nginx_https_enabled: true
177 |
178 | certbot_email: "you@email.me"
179 | certbot_domains:
180 | - "domain.com"
181 | - "www.domain.com"
182 | ```
183 |
184 | #### PostgreSQL Database Backups
185 | By default, daily backup is enabled in the `app-vars.yml` file. In order for this to work, the following variables need to be set. If you do not wish to store backups, remove (or uncomment) these lines from `app-vars.yml`.
186 |
187 | ```
188 | aws_key: "{{ vault_aws_key }}" # store this in group_vars/all/vault.yml that we created earlier
189 | aws_secret: "{{ vault_aws_secret }}"
190 |
191 | postgresql_backup_dir: "{{ deploy_user_path }}/backups"
192 | postgresql_backup_filename_format: >-
193 | {{ app_name }}-%Y%m%d-%H%M%S.pgdump
194 | postgresql_db_backup_healthcheck: "NOTIFICATION_URL (eg: https://healthcheck.io/)" # optional
195 | postgresql_s3_backup_bucket: "DB_BACKUP_BUCKET" # name of the S3 bucket to store backups
196 | postgresql_s3_backup_hour: "3"
197 | postgresql_s3_backup_minute: "*"
198 | postgresql_s3_backup_delete_after: "7 days" # days after which old backups should be deleted
199 | ```
200 |
201 |
202 | #### Puma config
203 |
204 | Your Rails app needs to have a puma config file (usually in `/config/puma.rb`). Here's a sample -
205 |
206 | ```
207 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
208 | threads threads_count, threads_count
209 |
210 | port ENV.fetch("PORT") { 3000 }
211 |
212 | rails_env = ENV.fetch("RAILS_ENV") { "development" }
213 | environment rails_env
214 |
215 | if %w[production staging].member?(rails_env)
216 | app_dir = ENV.fetch("APP_DIR") { "YOUR_APP/current" }
217 | directory app_dir
218 |
219 | shared_dir = ENV.fetch("SHARED_DIR") { "YOUR_APP/shared" }
220 |
221 | # Logging
222 | stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true
223 |
224 | pidfile "#{shared_dir}/tmp/pids/puma.pid"
225 | state_path "#{shared_dir}/tmp/pids/puma.state"
226 |
227 | # Set up socket location
228 | bind "unix://#{shared_dir}/sockets/puma.sock"
229 |
230 | workers ENV.fetch("WEB_CONCURRENCY") { 2 }
231 | preload_app!
232 |
233 | elsif rails_env == "development"
234 | plugin :tmp_restart
235 | end
236 | ```
237 |
238 | ---
239 |
240 | ### Motivation
241 | I use Heroku to deploy my Rails apps. It makes deployment really easy and I've got no complaints. However, I always wanted to learn how it all works under the hood. Over the last couple of months, I decided to learn more about how to set up a server and deploy a Rails app to production. This project is a consolidation of my learnings.
242 |
243 | ---
244 |
245 | ### Credits
246 | * [Geerling Guy](https://github.com/geerlingguy) (for his wonderful book on Ansible)
247 | * [dresden-weekly/ansible-rails](https://github.com/dresden-weekly/ansible-rails)
248 |
249 | ---
250 |
251 | ### Questions, comments, suggestions?
252 | Please let me know if you run into any issues or if you have any questions. I'd be happy to help. I would also welcome any improvements/suggestions by way of pull requests.
253 |
254 |
255 | Bharani
256 | Founder @ [EmailThis.me](https://www.emailthis.me)
257 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | Vagrant.configure(2) do |config|
2 |
3 | config.vm.box = "hashicorp/bionic64"
4 |
5 | config.vm.network "private_network", ip: "192.168.50.2"
6 |
7 | config.vm.provision"ansible" do |ansible|
8 | ansible.compatibility_mode = '2.0'
9 | ansible.playbook = "provision.yml"
10 | ansible.extra_vars = { ansible_python_interpreter:"/usr/bin/python3" }
11 | end
12 |
13 | end
--------------------------------------------------------------------------------
/ansible.cfg:
--------------------------------------------------------------------------------
1 | [defaults]
2 | host_key_checking = False
3 | vault_password_file = ./.vault_pass
--------------------------------------------------------------------------------
/app-vars.yml:
--------------------------------------------------------------------------------
1 | ---
2 | app_name: YOUR_APP_NAME
3 | deploy_user: deployer
4 | deploy_group: "{{ deploy_user }}"
5 | deploy_user_path: "/home/{{ deploy_user }}"
6 |
7 | # App Git repo
8 | app_git_repo: "YOUR_GIT_REPO"
9 | app_git_branch: "master"
10 |
11 | # Rails app
12 | app_root_path: "{{ deploy_user_path }}/{{ app_name }}"
13 | app_current_path: "{{ app_root_path }}/current"
14 | app_releases_path: "{{ app_root_path }}/releases"
15 | app_shared_path: "{{ app_root_path }}/shared"
16 | app_pids_path: "{{ app_shared_path }}/tmp/pids"
17 | app_logs_path: "{{ app_shared_path }}/logs"
18 | app_sockets_path: "{{ app_shared_path }}/sockets"
19 | rails_db_pool: 20
20 | rails_environment: production
21 |
22 | # Puma
23 | puma_service_file: "puma.service.j2"
24 | puma_config_file: "{{ app_current_path }}/config/puma.rb"
25 | puma_socket: "{{ app_sockets_path }}/puma.sock"
26 | puma_web_concurrency: 2
27 |
28 | # Sidekiq
29 | sidekiq_service_file: "sidekiq.service.j2"
30 |
31 | # Ansistrano
32 | ansistrano_deploy_to: "{{ app_root_path }}"
33 | ansistrano_keep_releases: 3
34 | ansistrano_deploy_via: git
35 | ansistrano_git_repo: "{{ app_git_repo }}"
36 | ansistrano_git_branch: "{{ app_git_branch }}"
37 | ansistrano_after_cleanup_tasks_file: "{{ playbook_dir }}/deploy_tasks/after_cleanup.yml"
38 | ansistrano_git_identity_key_path: "~/.ssh/id_rsa"
39 | ansistrano_ensure_shared_paths_exist: yes
40 | ansistrano_ensure_basedirs_shared_files_exist: yes
41 |
42 | ansistrano_shared_paths:
43 | - log # log -> ../../shared/log
44 | - tmp # tmp -> ../../shared/tmp
45 | - vendor # vendor -> ../../shared/vendor
46 | - public/assets # For rails asset pipeline
47 | - public/packs # For webpacker
48 | - node_modules # For webpacker node_modules -> ../../shared/node_modules
49 |
50 | shared_files_to_copy:
51 | - { src: database.yml.j2, dest: config/database.yml }
52 |
53 | # Common
54 | required_packages:
55 | - zlib1g-dev
56 | - build-essential
57 | - libssl-dev
58 | - libreadline-dev
59 | - libyaml-dev
60 | - libxml2-dev
61 | - libxslt1-dev
62 | - libcurl4-openssl-dev
63 | - libffi-dev
64 | - dirmngr
65 | - gnupg
66 | - autoconf
67 | - bison
68 | - libreadline6-dev
69 | - libncurses5-dev
70 | - libgdbm5
71 | - libgdbm-dev
72 | - libpq-dev # postgresql client
73 | - libjemalloc-dev # jemalloc
74 |
75 | # Ruby
76 | ruby_version: 2.6.6
77 | rbenv_ruby_configure_opts: "RUBY_CONFIGURE_OPTS=--with-jemalloc"
78 | rbenv_root_path: "{{ deploy_user_path }}/.rbenv"
79 | rbenv_shell_rc_path: "{{ deploy_user_path }}/.bashrc"
80 | rubies_path: "{{ rbenv_root_path }}/versions"
81 | ruby_path: "{{ rubies_path }}/{{ ruby_version }}"
82 | rbenv_bin: "{{ rbenv_root_path }}/bin/rbenv"
83 | rbenv_bundle: "{{ rbenv_root_path }}/shims/bundle"
84 |
85 | # Nodejs
86 | nodejs_version: "12.x"
87 |
88 | # Postgresql
89 | postgresql_version: "9.6"
90 | postgresql_db_user: "{{ deploy_user }}_postgresql_user"
91 | postgresql_db_password: "{{ vault_postgresql_db_password }}" # from vault
92 | postgresql_db_name: "{{ app_name }}_production"
93 | postgresql_listen:
94 | - "localhost"
95 | - "{{ ansible_default_ipv4.address }}" # only if db is on a separate server
96 |
97 |
98 | # nginx
99 | nginx_https_enabled: false # replace after setting up certbot
100 | nginx_conf_template: "nginx.conf.j2"
101 |
102 |
103 | # certbot
104 | # certbot_email: "admin@{{ inventory_hostname }}"
105 | # certbot_domains:
106 | # - "{{ inventory_hostname }}"
107 | # - "www.{{ inventory_hostname }}"
108 |
109 |
110 | # PostgreSQL Backup to S3
111 | aws_key: "{{ vault_aws_key }}"
112 | aws_secret: "{{ vault_aws_secret }}"
113 |
114 | postgresql_backup_dir: "{{ deploy_user_path }}/backups"
115 | postgresql_backup_filename_format: >-
116 | {{ app_name }}-%Y%m%d-%H%M%S.pgdump
117 | postgresql_db_backup_healthcheck: "NOTIFICATION_URL (eg: https://healthcheck.io/)"
118 | postgresql_s3_backup_bucket: "DB_BACKUP_BUCKET"
119 | postgresql_s3_backup_hour: "3"
120 | postgresql_s3_backup_minute: "*"
121 | postgresql_s3_backup_delete_after: "7 days" # days after which old backups should be deleted
122 |
123 | # fluentbit
124 | fluentbit_inputs:
125 | - Name: tail
126 | Path: "{{ app_logs_path }}/production.log"
127 |
128 | fluentbit_outputs:
129 | - Name: http
130 | Match: "*"
131 | tls: On
132 | Host: "" # e.g: loggly or sumologic logs endpoint
133 | Port: 443
134 | URI: "" # e.g: /receiver/v1/http/{{ vault_sumologic_token }}
135 | Format: json_lines
136 | Json_Date_Key: timestamp
137 | Json_Date_Format: iso8601
138 | Retry_Limit: False
139 |
140 | logrotate_conf:
141 | - path: "ansible"
142 | conf: |
143 | "{{ app_current_path }}/log/*.log" {
144 | weekly
145 | size 100M
146 | missingok
147 | rotate 12
148 | compress
149 | delaycompress
150 | notifempty
151 | copytruncate
152 | }
--------------------------------------------------------------------------------
/deploy.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Deploy our Rails ap
3 | hosts: all
4 | become: true
5 | become_user: "{{ deploy_user }}"
6 |
7 | pre_tasks:
8 | - name: Setup app folder
9 | file:
10 | state: directory
11 | path: "{{ app_root_path }}"
12 | owner: "{{ deploy_user }}"
13 | group: "{{ deploy_group }}"
14 |
15 | - name: Copy rbenv-vars file
16 | template:
17 | src: ".rbenv-vars.j2"
18 | dest: "{{ app_root_path }}/.rbenv-vars"
19 | owner: "{{ deploy_user }}"
20 | group: "{{ deploy_group }}"
21 |
22 | - name: Make shared directories
23 | file:
24 | path: "{{ app_shared_path }}/{{ item }}"
25 | state: directory
26 | owner: "{{ deploy_user }}"
27 | group: "{{ deploy_group }}"
28 | with_items:
29 | - tmp
30 | - tmp/pids
31 | - tmp/cache
32 | - sockets
33 | - log
34 | - public
35 | - public/packs
36 | - vendor
37 | - vendor/bundle
38 | - bin
39 | - config
40 | - config/puma
41 | - assets
42 | - node_modules
43 |
44 | - name: Upload shared files
45 | template:
46 | src: "{{ item.src }}"
47 | dest: "{{ app_shared_path }}/{{ item.dest }}"
48 | owner: "{{ deploy_user }}"
49 | group: "{{ deploy_group }}"
50 | with_items: "{{ shared_files_to_copy }}"
51 | tags:
52 | - copy
53 |
54 | roles:
55 | - role: ansistrano.deploy
56 |
57 | - role: puma
58 | tags: puma
59 | become: true
60 | become_user: root
61 |
62 | - role: sidekiq
63 | tags: sidekiq
64 | become: true
65 | become_user: root
--------------------------------------------------------------------------------
/deploy_tasks/after_cleanup.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Bundle install with --deploy
3 | bundler:
4 | state: present
5 | deployment_mode: yes
6 | gem_path: "../../shared/vendor/bundle" # relative to chdir
7 | chdir: "{{ ansistrano_release_path.stdout }}"
8 | exclude_groups: ["development", "test"]
9 | executable: "{{ rbenv_bundle }}"
10 |
11 | - name: Running pending migrations
12 | shell: "{{ rbenv_bundle }} exec rake db:migrate"
13 | run_once: true
14 | args:
15 | chdir: "{{ ansistrano_release_path.stdout }}"
16 |
17 | - name: Precompiling assets
18 | shell: "{{ rbenv_bundle }} exec rake assets:precompile"
19 | args:
20 | chdir: "{{ ansistrano_release_path.stdout }}"
--------------------------------------------------------------------------------
/group_vars/all/vault.yml:
--------------------------------------------------------------------------------
1 | $ANSIBLE_VAULT;1.1;AES256
2 | 30373963356363633262326462656435666536663065623465313933643862636635313936373330
3 | 3039633537316134626331323735643638326231616465310a383832663835353839643138323862
4 | 31663337326562646630343430356631663261393965386431323134623832336566623561633161
5 | 3733633864613765380a666138373235353465363234343534633966663136666634346234623764
6 | 39346533323631346538393564613637316166656165613034656363623037363033613065356263
7 | 6666343833303934353732653765656530396131383936366266
8 |
--------------------------------------------------------------------------------
/images/ansible-rails-promo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EmailThis/ansible-rails/ff5b39db6d4e678edc0cd14fbd943f8e50c10e51/images/ansible-rails-promo.jpg
--------------------------------------------------------------------------------
/inventories/development.ini:
--------------------------------------------------------------------------------
1 | [web]
2 | 192.168.50.2
3 |
4 | [all:vars]
5 | ansible_ssh_user=deployer
6 | ansible_python_interpreter=/usr/bin/python3
7 |
--------------------------------------------------------------------------------
/provision.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - hosts: all
3 | become: true
4 |
5 | vars_files:
6 | - app-vars
7 |
8 | roles:
9 | - role: common
10 | - role: user
11 | - role: ssh
12 | - role: ruby
13 | tags: ruby
14 | - role: nodejs
15 | tags: nodejs
16 | - role: yarn
17 | tags: nodejs
18 | - role: postgresql
19 | tags: postgresql
20 | - role: redis
21 | tags: redis
22 | - role: nginx
23 | tags: nginx
24 | - role: logrotate
25 | tags: logrotate
--------------------------------------------------------------------------------
/roles/certbot/defaults/main.yml:
--------------------------------------------------------------------------------
1 | certbot_url: https://dl.eff.org/certbot-auto
2 | certbot_dir: /opt/certbot
3 | certbot_email: admin@example.com
4 | certbot_flags: ""
5 | certbot_domains: []
--------------------------------------------------------------------------------
/roles/certbot/tasks/cert.yml:
--------------------------------------------------------------------------------
1 | - name: Check if a certificate already exists
2 | stat:
3 | path: /etc/letsencrypt/live/{{ domain | replace('*.', '') }}/cert.pem
4 | register: letsencrypt_cert
5 |
6 | - name: Get new certificate
7 | command: "{{ certbot_dir }}/certbot-auto certonly --non-interactive --quiet --agree-tos --email {{ certbot_email }} --standalone -d {{ domain }} {{ certbot_flags }}"
8 | when: not letsencrypt_cert.stat.exists
9 | notify: Restart nginx
--------------------------------------------------------------------------------
/roles/certbot/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Create certbot directory
3 | file: path=/opt/certbot state=directory mode=0755 owner=root group=root
4 |
5 | - name: Install certbot standalone
6 | get_url:
7 | url: "{{ certbot_url }}"
8 | dest: "{{ certbot_dir }}/certbot-auto"
9 |
10 | - name: Ensure that certbot-auto is executable
11 | file:
12 | path: "{{ certbot_dir }}/certbot-auto"
13 | mode: 0755
14 |
15 | - name: Ensure that nginx is stopped
16 | service:
17 | name: nginx
18 | state: stopped
19 |
20 | - name: Check & get new certificate
21 | include_tasks: cert.yml
22 | loop: "{{ certbot_domains }}"
23 | loop_control:
24 | loop_var: domain
25 |
26 | - name: Add certbot renewal cronjob
27 | cron: name="renew letsencrypt certificates" hour="0" minute="0" job="/bin/bash {{ certbot_dir }}/certbot-auto renew --non-interactive --quiet --standalone --pre-hook 'service nginx stop' --post-hook 'service nginx start'"
--------------------------------------------------------------------------------
/roles/common/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | required_packages: []
--------------------------------------------------------------------------------
/roles/common/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install prerequisites
3 | apt: name=aptitude update_cache=yes state=latest force_apt_get=yes
4 |
5 | - name: Install required system packages
6 | apt: name={{ item }} state=latest update_cache=yes
7 | loop:
8 | - curl
9 | - ufw
10 | - fail2ban
11 | - git-core
12 | - apt-transport-https
13 | - ca-certificates
14 | - software-properties-common
15 | - python3-pip
16 | - virtualenv
17 | - python3-setuptools
18 | - "{{ required_packages }}"
--------------------------------------------------------------------------------
/roles/fluentbit/defaults/main.yml:
--------------------------------------------------------------------------------
1 | fluentbit_flush_seconds: 2
2 |
3 | # Default inputs
4 | fluentbit_inputs: []
5 |
6 | # Default outputs
7 | fluentbit_outputs: []
8 |
9 | fluentbit_hostname: "{{ hostname }}"
--------------------------------------------------------------------------------
/roles/fluentbit/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Restart fluentbit
3 | service:
4 | name: td-agent-bit
5 | enabled: true
6 | state: restarted
7 | become: true
--------------------------------------------------------------------------------
/roles/fluentbit/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add td-agent-bit apt-key
3 | apt_key:
4 | url: https://packages.fluentbit.io/fluentbit.key
5 | state: present
6 |
7 | - name: Add td-agent-bit repository
8 | apt_repository:
9 | repo: 'deb https://packages.fluentbit.io/ubuntu/bionic bionic main'
10 | state: present
11 | filename: td-agent-bit
12 | update_cache: true
13 |
14 | - name: Install fluentbit package
15 | package:
16 | name: td-agent-bit
17 | state: present
18 | update_cache: true
19 | notify: Restart fluentbit
20 |
21 | - name: Configure td-agent-bit.conf file
22 | template:
23 | src: td-agent-bit.conf.j2
24 | dest: /etc/td-agent-bit/td-agent-bit.conf
25 | mode: 0644
26 | notify: Restart fluentbit
--------------------------------------------------------------------------------
/roles/fluentbit/templates/td-agent-bit.conf.j2:
--------------------------------------------------------------------------------
1 | [SERVICE]
2 | Flush {{ fluentbit_flush_seconds }}
3 |
4 | {% for input in fluentbit_inputs %}
5 | [INPUT]
6 | {% for key in input %}
7 | {{ key }} {{ input[key] }}
8 | {% endfor %}
9 | {% endfor %}
10 |
11 | {% for output in fluentbit_outputs %}
12 | [OUTPUT]
13 | {% for key in output %}
14 | {{ key }} {{ output[key] }}
15 | {% endfor %}
16 | {% endfor %}
17 |
18 | [FILTER]
19 | Name record_modifier
20 | Match *
21 | Record hostname {{ fluentbit_hostname }}
--------------------------------------------------------------------------------
/roles/logrotate/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | logrotate_conf: []
--------------------------------------------------------------------------------
/roles/logrotate/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install logrotate
3 | apt: name=logrotate state=latest update_cache=yes
4 |
5 | - blockinfile:
6 | path: "/etc/logrotate.d/{{ item.path }}"
7 | block: "{{ item.conf }}"
8 | create: yes
9 | loop: "{{ logrotate_conf }}"
--------------------------------------------------------------------------------
/roles/nginx/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | nginx_conf_template: "nginx-default.conf.j2"
3 | nginx_https_enabled: false
--------------------------------------------------------------------------------
/roles/nginx/files/config/general.conf:
--------------------------------------------------------------------------------
1 | # favicon.ico
2 | location = /favicon.ico {
3 | log_not_found off;
4 | access_log off;
5 | }
6 |
7 | # robots.txt
8 | location = /robots.txt {
9 | log_not_found off;
10 | access_log off;
11 | }
12 |
13 | # gzip
14 | gzip on;
15 | gzip_vary on;
16 | gzip_proxied any;
17 | gzip_comp_level 6;
18 | gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
19 |
20 | # remove trailing slashes
21 | rewrite ^/(.*)/$ /$1 permanent;
--------------------------------------------------------------------------------
/roles/nginx/files/config/letsencrypt.conf:
--------------------------------------------------------------------------------
1 | # ACME-challenge
2 | location ^~ /.well-known/acme-challenge/ {
3 | root /var/www/_letsencrypt;
4 | }
5 |
--------------------------------------------------------------------------------
/roles/nginx/files/config/proxy.conf:
--------------------------------------------------------------------------------
1 | proxy_http_version 1.1;
2 | proxy_cache_bypass $http_upgrade;
3 |
4 | proxy_set_header Upgrade $http_upgrade;
5 | proxy_set_header Connection "upgrade";
6 | proxy_set_header Host $host;
7 | proxy_set_header X-Real-IP $remote_addr;
8 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
9 | proxy_set_header X-Forwarded-Proto $scheme;
10 | proxy_set_header X-Forwarded-Host $host;
11 | proxy_set_header X-Forwarded-Port $server_port;
12 |
--------------------------------------------------------------------------------
/roles/nginx/files/config/security.conf:
--------------------------------------------------------------------------------
1 | # security headers
2 | add_header X-Frame-Options "SAMEORIGIN" always;
3 | add_header X-XSS-Protection "1; mode=block" always;
4 | add_header X-Content-Type-Options "nosniff" always;
5 | add_header Referrer-Policy "no-referrer-when-downgrade" always;
6 | add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
7 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
8 |
9 | # . files
10 | location ~ /\.(?!well-known) {
11 | deny all;
12 | }
13 |
--------------------------------------------------------------------------------
/roles/nginx/files/nginx.conf:
--------------------------------------------------------------------------------
1 | # Generated by nginxconfig.io
2 | # https://www.digitalocean.com/community/tools/nginx#?0.domain=example.com&0.php=false&0.proxy&0.root=false&server_tokens&limit_req&brotli&expires_assets=14d&expires_media=14d&expires_svg=14d&expires_fonts=14d&client_max_body_size=25
3 |
4 | user www-data;
5 | pid /run/nginx.pid;
6 | worker_processes auto;
7 | worker_rlimit_nofile 65535;
8 |
9 | events {
10 | multi_accept on;
11 | worker_connections 65535;
12 | }
13 |
14 | http {
15 | charset utf-8;
16 | sendfile on;
17 | tcp_nopush on;
18 | tcp_nodelay on;
19 | log_not_found off;
20 | types_hash_max_size 2048;
21 | client_max_body_size 25M;
22 |
23 | # MIME
24 | include mime.types;
25 | default_type application/octet-stream;
26 |
27 | # logging
28 | access_log /var/log/nginx/access.log;
29 | error_log /var/log/nginx/error.log warn;
30 |
31 | # limits
32 | limit_req_log_level warn;
33 | limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m;
34 |
35 | # SSL
36 | ssl_session_timeout 1d;
37 | ssl_session_cache shared:SSL:10m;
38 | ssl_session_tickets off;
39 |
40 | # Diffie-Hellman parameter for DHE ciphersuites
41 | ssl_dhparam /etc/nginx/dhparam.pem;
42 |
43 | # Mozilla Intermediate configuration
44 | ssl_protocols TLSv1.2 TLSv1.3;
45 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
46 |
47 | # OCSP Stapling
48 | ssl_stapling on;
49 | ssl_stapling_verify on;
50 | resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
51 | resolver_timeout 2s;
52 |
53 | # load configs
54 | include /etc/nginx/conf.d/*.conf;
55 | include /etc/nginx/sites-enabled/*;
56 | }
57 |
--------------------------------------------------------------------------------
/roles/nginx/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Restart nginx
3 | service:
4 | name: nginx
5 | state: restarted
6 |
7 | - name: Reload nginx
8 | service:
9 | name: nginx
10 | state: reloaded
--------------------------------------------------------------------------------
/roles/nginx/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add nginx repo
3 | apt_repository:
4 | repo: ppa:nginx/stable
5 |
6 | - name: Install nginx
7 | apt:
8 | name: nginx
9 | state: present
10 | force: yes
11 | update_cache: yes
12 |
13 | - name: Copy nginx config files
14 | copy:
15 | src: "{{ item }}"
16 | dest: /etc/nginx
17 | owner: "{{ deploy_user }}"
18 | group: "{{ deploy_group }}"
19 | with_items:
20 | - nginx.conf
21 | - config
22 |
23 | - name: Generate DH param (2048 bits)
24 | openssl_dhparam:
25 | path: /etc/nginx/dhparam.pem
26 | size: 2048
27 | when: nginx_https_enabled == true
28 |
29 | - name: Create a directory if it does not exist
30 | file:
31 | path: "/etc/nginx/{{ item }}"
32 | state: directory
33 | owner: "{{ deploy_user }}"
34 | group: "{{ deploy_group }}"
35 | mode: 0644
36 | with_items:
37 | - sites-available
38 | - sites-enabled
39 |
40 | - name: Copy nginx configuration in place
41 | template:
42 | src: "{{ nginx_conf_template }}"
43 | dest: "/etc/nginx/sites-available/default"
44 | owner: "{{ deploy_user }}"
45 | group: "{{ deploy_group }}"
46 | mode: 0644
47 |
48 | - name: Symlink default site
49 | file:
50 | src: /etc/nginx/sites-available/default
51 | dest: /etc/nginx/sites-enabled/default
52 | state: link
53 |
54 | - name: Set nginx user
55 | lineinfile:
56 | dest: /etc/nginx/nginx.conf
57 | regexp: "^user"
58 | line: "user {{ deploy_user }};"
59 | state: present
60 |
61 | # No need to restart nginx nginx_https_enabled is set to true
62 | # because certbot will install the certificates and then restart nginx
63 | - name: Restart nginx (when not running certbot)
64 | command: "/bin/true"
65 | notify:
66 | - Reload nginx
67 | - Restart nginx
68 | when: nginx_https_enabled != true
69 |
70 |
--------------------------------------------------------------------------------
/roles/nginx/templates/nginx-default.conf.j2:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | listen [::]:80;
4 |
5 | server_name {{ inventory_hostname }};
6 |
7 | location / {
8 | return 200 "ok";
9 | add_header Content-Type text/plain;
10 | }
11 | }
--------------------------------------------------------------------------------
/roles/nodejs/defaults/main.yml:
--------------------------------------------------------------------------------
1 | nodejs_version: "12.x"
2 |
--------------------------------------------------------------------------------
/roles/nodejs/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install dependencies
3 | apt:
4 | name:
5 | - apt-transport-https
6 | - gnupg2
7 | state: present
8 |
9 | - name: Add Nodesource apt key
10 | apt_key:
11 | url: https://keyserver.ubuntu.com/pks/lookup?op=get&fingerprint=on&search=0x1655A0AB68576280
12 | id: "68576280"
13 | state: present
14 |
15 | - name: Add NodeSource repositories
16 | apt_repository:
17 | repo: "{{ item }}"
18 | state: present
19 | with_items:
20 | - "deb https://deb.nodesource.com/node_{{ nodejs_version }} {{ ansible_distribution_release }} main"
21 | - "deb-src https://deb.nodesource.com/node_{{ nodejs_version }} {{ ansible_distribution_release }} main"
22 | register: node_repo
23 |
24 | - name: Update apt cache if repo was added
25 | apt: update_cache=yes
26 | when: node_repo.changed
27 |
28 | - name: Ensure Node.js and npm are installed
29 | apt:
30 | name: "nodejs={{ nodejs_version|regex_replace('x', '') }}*"
31 | state: present
--------------------------------------------------------------------------------
/roles/pgbackup/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | postgresql_backup_dir: "backups"
3 | postgresql_backup_filename_format: >-
4 | {{ app_name }}-%Y%m%d-%H%M%S.pgdump
5 | postgresql_db_backup_healthcheck: ""
6 | postgresql_s3_backup_hour: "3"
7 | postgresql_s3_backup_minute: "*"
8 | postgresql_s3_backup_delete_after: "7 days"
--------------------------------------------------------------------------------
/roles/pgbackup/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Create postgresql backup directory
2 | file:
3 | path: "{{ postgresql_backup_dir }}"
4 | recurse: true
5 | state: directory
6 |
7 | - name: Set backup directory permissions
8 | file:
9 | path: "{{ postgresql_backup_dir }}"
10 | state: directory
11 | owner: "{{ deploy_user }}"
12 | group: "{{ deploy_group }}"
13 | mode: 0700
14 |
15 | - name: Upload backup script
16 | become: true
17 | template:
18 | src: postgresql-backup.j2
19 | dest: "{{ postgresql_backup_dir }}/postgresql-backup.sh"
20 | mode: 0755
21 |
22 | - name: Configure backup cron job
23 | cron:
24 | name: Backup cron job
25 | minute: "{{ postgresql_s3_backup_minute }}"
26 | hour: "{{ postgresql_s3_backup_hour }}"
27 | user: "{{ deploy_user }}"
28 | job: "bash -lc {{ postgresql_backup_dir }}/postgresql-backup.sh"
29 | cron_file: "postgresql-backup"
30 | state: present
31 |
32 |
--------------------------------------------------------------------------------
/roles/pgbackup/templates/postgresql-backup.j2:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
5 |
6 | NOW=$(date +"%Y-%m-%d-at-%H-%M-%S")
7 | FILENAME="{{ postgresql_db_name }}"-"$NOW"
8 |
9 | DELETION_TIMESTAMP=`[ "$(uname)" = Linux ] && date +%s --date="-{{ postgresql_s3_backup_delete_after }}"`
10 |
11 | echo " * Generating backup";
12 | PGPASSWORD={{ postgresql_db_password }} pg_dump -Fc --no-acl --no-owner -h localhost -U {{ postgresql_db_user }} {{ postgresql_db_name }} > {{ postgresql_backup_dir }}/"$FILENAME".dump
13 |
14 | echo " * Uploading to S3";
15 | aws s3 cp {{ postgresql_backup_dir }}/"$FILENAME".dump s3://{{ postgresql_s3_backup_bucket }}/"$FILENAME".dump
16 |
17 | echo " * Delete local file";
18 | # rm {{ postgresql_backup_dir }}/"$FILENAME".dump
19 |
20 |
21 | # Delete old files
22 | echo " * Deleting old backups...";
23 |
24 | # Loop through files
25 | aws s3 ls s3://{{ postgresql_s3_backup_bucket }}/ | while read -r line; do
26 | # Get file creation date
27 | createDate=`echo $line|awk {'print $1" "$2'}`
28 | createDate=`date -d"$createDate" +%s`
29 |
30 | if [[ $createDate -lt $DELETION_TIMESTAMP ]]
31 | then
32 | # Get file name
33 | FILENAME=`echo $line|awk {'print $4'}`
34 | if [[ $FILENAME != "" ]]
35 | then
36 | echo " -> Deleting $FILENAME"
37 | aws s3 rm s3://{{ postgresql_s3_backup_bucket }}/$FILENAME
38 | fi
39 | fi
40 | done;
41 |
42 | echo " * Ping Healthcheck URL";
43 | curl -fsS --retry 3 {{ postgresql_db_backup_healthcheck }}
44 |
--------------------------------------------------------------------------------
/roles/postgresql/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | postgresql_version: "9.6"
3 |
4 | postgresql_packages:
5 | - "postgresql-{{ postgresql_version }}"
6 | - "postgresql-contrib-{{ postgresql_version }}"
7 |
8 | postgresql_python_packages:
9 | - "python3-psycopg2"
10 |
11 | postgresql_apt_url: "https://apt.postgresql.org/pub/repos/apt"
12 | postgresql_apt_key: "{{ postgresql_apt_url }}/ACCC4CF8.asc"
13 | postgresql_apt_repo: "deb {{ postgresql_apt_url }}/ {{ ansible_lsb.codename }}-pgdg main"
14 |
15 | postgresql_config_path: /etc/postgresql/{{ postgresql_version }}/main
16 | postgresql_data_path: /var/lib/postgresql/{{ postgresql_version }}/main
17 | postgresql_pid_file: /var/run/postgresql/{{ postgresql_version }}-main.pid
18 |
19 | # default admin user
20 | postgresql_admin_user: postgres
21 |
22 | # table locale and character encoding
23 | postgresql_locale: "en_US"
24 | postgresql_encoding: "UTF-8"
25 |
26 | # shell locale and character encoding
27 | postgresql_shell_locale: "{{ postgresql_locale }}"
28 | postgresql_shell_encoding: "{{ postgresql_encoding | replace('-', '') | lower }}"
29 |
30 | pg_hba_template: "pg_hba.conf.j2"
31 | postgresql_parameters_template: "postgresql.conf.j2"
32 |
33 | # default application database
34 | postgresql_db_user: '' # name of the user (empty means no user is created)
35 | postgresql_db_password: ''
36 | postgresql_db_name: '' # name of the database (empty means no database is created)
37 |
38 | # all the created users
39 | postgresql_users:
40 | - name: "{{ postgresql_db_user | default('') }}"
41 | password: "{{ postgresql_db_password | default('') }}" # only needed if user is given
42 | role_attr_flags: "{{ postgresql_users_role_attr_flags }}"
43 |
44 | # all the created databases
45 | postgresql_databases:
46 | - name: "{{ postgresql_db_name }}"
47 | owner: "{{ postgresql_db_user | default('') }}" # empty mean 'postgres' user will own it
48 | # encoding: "{{ postgresql_encoding }}"
49 | # lc_collate: "{{ postgresql_locale }}.{{ postgresql_encoding }}"
50 | # lc_ctype: "{{ postgresql_locale }}.{{ postgresql_encoding }}"
51 | # template: template0
52 |
53 | # default user attr_flags
54 | postgresql_users_role_attr_flags:
55 | - CREATEDB
56 | - NOSUPERUSER
57 |
58 |
59 | # variables to build postgresql.conf
60 | # postgresql_host: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}"
61 | postgresql_listen:
62 | - "localhost"
63 |
64 | postgresql_port: 5432
65 | postgresql_max_connections: 100
66 |
67 | postgresql_connections:
68 | ssl: false
69 |
70 | postgresql_resources:
71 | shared_buffers: 128MB
72 |
73 | postgresql_write_ahead_log: {}
74 | postgresql_replication: {}
75 | postgresql_query_tuning: {}
76 | postgresql_logging:
77 | log_line_prefix: "'%t '"
78 | log_timezone: "'UTC'"
79 |
80 | postgresql_runtime_statistics: {}
81 | postgresql_autovacuum: {}
82 | postgresql_client_connection_defaults:
83 | datestyle: "'iso, mdy'"
84 | timezone: "'UTC'"
85 | lc_messages: "'{{ postgresql_locale }}.{{ postgresql_encoding }}'"
86 | lc_monetary: "'{{ postgresql_locale }}.{{ postgresql_encoding }}'"
87 | lc_numeric: "'{{ postgresql_locale }}.{{ postgresql_encoding }}'"
88 | lc_time: "'{{ postgresql_locale }}.{{ postgresql_encoding }}'"
89 | default_text_search_config: "'pg_catalog.english'"
90 |
91 | postgresql_lock_management: {}
92 | postgresql_cutomized: {}
93 |
94 | postgresql_service: postgresql
95 |
96 | postgresql_socket_directories:
97 | - "/var/run/postgresql"
--------------------------------------------------------------------------------
/roles/postgresql/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: Restart postgresql
2 | service:
3 | name: "{{ postgresql_service }}"
4 | state: restarted
--------------------------------------------------------------------------------
/roles/postgresql/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Set shell locales
3 | copy:
4 | dest: /etc/profile.d/lang.sh
5 | content: |
6 | export LANGUAGE="{{ postgresql_locale }}.{{ postgresql_shell_encoding }}"
7 | export LANG="{{ postgresql_locale }}.{{ postgresql_shell_encoding }}"
8 | export LC_ALL="{{ postgresql_locale }}.{{ postgresql_shell_encoding }}"
9 |
10 | - name: Add postgres repo key
11 | apt_key:
12 | url: "{{ postgresql_apt_key }}"
13 |
14 | - name: Add postgres repo
15 | apt_repository:
16 | repo: "{{ postgresql_apt_repo }}"
17 |
18 | - name: Install required postgres packages
19 | apt: name={{ item }} state=latest update_cache=yes cache_valid_time=86400
20 | loop:
21 | - "{{ postgresql_packages }}"
22 | - "{{ postgresql_python_packages }}"
23 | - "postgresql-{{ postgresql_version }}"
24 | - "postgresql-contrib-{{ postgresql_version }}"
25 |
26 | - name: Configure pg_hba.conf
27 | template:
28 | src: "{{ pg_hba_template }}"
29 | dest: "{{ postgresql_config_path }}/pg_hba.conf"
30 |
31 | - name: Configure postgresql.conf
32 | template:
33 | src: "{{ postgresql_parameters_template }}"
34 | dest: "{{ postgresql_config_path }}/postgresql.conf"
35 | notify:
36 | - Restart postgresql
37 |
38 | - meta: flush_handlers
39 |
40 | - name: Template locales
41 | shell: >
42 | psql -c "update pg_database
43 | set
44 | encoding = pg_char_to_encoding('{{ postgresql_encoding }}'),
45 | datctype = '{{ postgresql_locale }}.{{ postgresql_encoding }}',
46 | datcollate = '{{ postgresql_locale }}.{{ postgresql_encoding }}'
47 | where
48 | encoding != pg_char_to_encoding('{{ postgresql_encoding }}')
49 | or datctype != '{{ postgresql_locale }}.{{ postgresql_encoding }}'
50 | or datcollate != '{{ postgresql_locale }}.{{ postgresql_encoding }}';"
51 | register: postgresql_update_template_result
52 | changed_when: >
53 | postgresql_update_template_result.stdout is defined
54 | and 'UPDATE 0' != postgresql_update_template_result.stdout
55 | ignore_errors: yes
56 | become: yes
57 | become_user: "{{ postgresql_admin_user }}"
58 |
59 | - name: Create users
60 | postgresql_user:
61 | name: "{{ item.name }}"
62 | password: "{{ item.password }}"
63 | role_attr_flags: "{{ item.role_attr_flags | default(postgresql_users_role_attr_flags) | join(',') | replace('\\n', '') }}"
64 | when: item.name != ''
65 | with_items: "{{ postgresql_users }}"
66 | become: yes
67 | become_user: "{{ postgresql_admin_user }}"
68 |
69 | - name: Create databases
70 | postgresql_db:
71 | name: "{{ item.name }}"
72 | owner: "{{ item.owner | default(postgresql_user, true) }}"
73 | encoding: "{{ item.encoding | default(postgresql_encoding) }}"
74 | lc_collate: "{{ item.lc_collate | default(postgresql_locale + '.' + postgresql_encoding) }}"
75 | lc_ctype: "{{ item.lc_ctype | default(postgresql_locale + '.' + postgresql_encoding) }}"
76 | template: "{{ item.template | default('template0') }}"
77 | state: present
78 | when: item.name != ''
79 | with_items: "{{ postgresql_databases }}"
80 | become: yes
81 | become_user: "{{ postgresql_admin_user }}"
82 |
--------------------------------------------------------------------------------
/roles/postgresql/templates/pg_hba.conf.j2:
--------------------------------------------------------------------------------
1 | local all postgres peer
2 | local all all peer
3 | host all all 127.0.0.1/32 md5
4 | host all all ::1/128 md5
--------------------------------------------------------------------------------
/roles/postgresql/templates/postgresql.conf.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | # -----------------------------
4 | # PostgreSQL configuration file
5 | # -----------------------------
6 |
7 | #------------------------------------------------------------------------------
8 | # FILE LOCATIONS
9 | #------------------------------------------------------------------------------
10 |
11 | data_directory = '{{ postgresql_data_path }}'
12 | hba_file = '{{ postgresql_config_path }}/pg_hba.conf'
13 | ident_file = '{{ postgresql_config_path }}/pg_ident.conf'
14 | external_pid_file = '{{ postgresql_pid_file }}'
15 |
16 | #------------------------------------------------------------------------------
17 | # CONNECTIONS AND AUTHENTICATION
18 | #------------------------------------------------------------------------------
19 |
20 | listen_addresses = '{{ postgresql_listen | join(',') }}'
21 | port = {{ postgresql_port }}
22 | max_connections = {{ postgresql_max_connections }}
23 | unix_socket_directories = '{{ postgresql_socket_directories | join(',') }}'
24 | {% for k,v in postgresql_connections.items() | list %}
25 | {{ k }} = {{ v }}
26 | {% endfor %}
27 |
28 | #------------------------------------------------------------------------------
29 | # RESOURCE USAGE (except WAL)
30 | #------------------------------------------------------------------------------
31 |
32 | {% for k,v in postgresql_resources.items() | list %}
33 | {{ k }} = {{ v }}
34 | {% endfor %}
35 |
36 | #------------------------------------------------------------------------------
37 | # WRITE AHEAD LOG
38 | #------------------------------------------------------------------------------
39 |
40 | {% for k,v in postgresql_write_ahead_log.items() | list %}
41 | {{ k }} = {{ v }}
42 | {% endfor %}
43 |
44 | #------------------------------------------------------------------------------
45 | # REPLICATION
46 | #------------------------------------------------------------------------------
47 |
48 | {% for k,v in postgresql_replication.items() | list %}
49 | {{ k }} = {{ v }}
50 | {% endfor %}
51 |
52 | #------------------------------------------------------------------------------
53 | # QUERY TUNING
54 | #------------------------------------------------------------------------------
55 |
56 | {% for k,v in postgresql_query_tuning.items() | list %}
57 | {{ k }} = {{ v }}
58 | {% endfor %}
59 |
60 | #------------------------------------------------------------------------------
61 | # ERROR REPORTING AND LOGGING
62 | #------------------------------------------------------------------------------
63 |
64 | {% for k,v in postgresql_logging.items() | list %}
65 | {{ k }} = {{ v }}
66 | {% endfor %}
67 |
68 | #------------------------------------------------------------------------------
69 | # RUNTIME STATISTICS
70 | #------------------------------------------------------------------------------
71 |
72 | {% for k,v in postgresql_runtime_statistics.items() | list %}
73 | {{ k }} = {{ v }}
74 | {% endfor %}
75 |
76 | #------------------------------------------------------------------------------
77 | # AUTOVACUUM PARAMETERS
78 | #------------------------------------------------------------------------------
79 |
80 | {% for k,v in postgresql_autovacuum.items() | list %}
81 | {{ k }} = {{ v }}
82 | {% endfor %}
83 |
84 | #------------------------------------------------------------------------------
85 | # CLIENT CONNECTION DEFAULTS
86 | #------------------------------------------------------------------------------
87 |
88 | {% for k,v in postgresql_client_connection_defaults.items() | list %}
89 | {{ k }} = {{ v }}
90 | {% endfor %}
91 |
92 | #------------------------------------------------------------------------------
93 | # LOCK MANAGEMENT
94 | #------------------------------------------------------------------------------
95 |
96 | {% for k,v in postgresql_lock_management.items() | list %}
97 | {{ k }} = {{ v }}
98 | {% endfor %}
99 |
100 | #------------------------------------------------------------------------------
101 | # CUSTOMIZED OPTIONS
102 | #------------------------------------------------------------------------------
103 |
104 | {% for k,v in postgresql_cutomized.items() | list %}
105 | {{ k }} = {{ v }}
106 | {% endfor %}
--------------------------------------------------------------------------------
/roles/puma/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Copy puma.service
3 | template:
4 | src: "{{ puma_service_file }}"
5 | dest: /lib/systemd/system/puma.service
6 | force: yes
7 | owner: "{{ deploy_user }}"
8 | group: "{{ deploy_group }}"
9 | mode: 0644
10 | register: puma_service_file
11 |
12 | - name: Ensure that we re-read puma.service
13 | systemd:
14 | daemon_reload: yes
15 | name: "puma"
16 | when: puma_service_file.changed
17 |
18 | - name: Enable puma
19 | service:
20 | name: puma
21 | enabled: yes
22 |
23 | - name: Restart puma
24 | service:
25 | name: puma
26 | state: restarted
27 |
--------------------------------------------------------------------------------
/roles/redis/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install Redis Server
3 | apt: name=redis-server state=latest
4 |
5 | - name: Install Redis Tools
6 | apt: name=redis-tools state=latest
7 |
8 | - name: Ensure Redis Server is running
9 | service: name=redis-server state=started enabled=yes
--------------------------------------------------------------------------------
/roles/ruby/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ruby_version: 2.6.6
2 | additional_rubies: []
--------------------------------------------------------------------------------
/roles/ruby/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install rbenv
3 | become: yes
4 | become_user: "{{ deploy_user }}"
5 | git:
6 | repo: "https://github.com/rbenv/rbenv.git"
7 | dest: "{{ rbenv_root_path }}"
8 | depth: 1
9 | accept_hostkey: yes
10 | clone: yes
11 | update: yes
12 |
13 | - name: Install ruby-build
14 | become: yes
15 | become_user: "{{ deploy_user }}"
16 | git:
17 | repo: "https://github.com/rbenv/ruby-build.git"
18 | dest: "{{ rbenv_root_path }}/plugins/ruby-build"
19 | depth: 1
20 |
21 | - name: Install rbenv-vars
22 | become: yes
23 | become_user: "{{ deploy_user }}"
24 | git:
25 | repo: "https://github.com/rbenv/rbenv-vars.git"
26 | dest: "{{ rbenv_root_path }}/plugins/rbenv-vars"
27 | depth: 1
28 |
29 | - name: Ensure {{ rbenv_shell_rc }} exists
30 | become: true
31 | become_user: "{{ deploy_user }}"
32 | shell: "touch {{ rbenv_shell_rc_path }}"
33 | args:
34 | creates: "{{ rbenv_shell_rc_path }}"
35 |
36 | - name: Export RBENV_ROOT in {{ rbenv_shell_rc_path }}
37 | become: true
38 | become_user: "{{ deploy_user }}"
39 | lineinfile:
40 | dest: "{{ rbenv_shell_rc_path }}"
41 | regexp: "^export RBENV_ROOT="
42 | line: "export RBENV_ROOT={{ rbenv_root_path }}"
43 |
44 | - name: Put rbenv in users PATH in {{ rbenv_shell_rc_path }}
45 | become: true
46 | become_user: "{{ deploy_user }}"
47 | lineinfile:
48 | dest: "{{ rbenv_shell_rc_path }}"
49 | regexp: "^PATH=\\$PATH:\\$RBENV_ROOT/bin"
50 | line: "PATH=$RBENV_ROOT/bin:$PATH"
51 |
52 | - name: Put $RBENV_ROOT/shims in users $PATH in {{ rbenv_shell_rc_path }}
53 | become: true
54 | become_user: "{{ deploy_user }}"
55 | lineinfile:
56 | dest: "{{ rbenv_shell_rc_path }}"
57 | regexp: "^PATH=\\$RBENV_ROOT/shims:\\$PATH"
58 | line: "PATH=$RBENV_ROOT/shims:$PATH"
59 |
60 | - name: Install Rubies
61 | become: yes
62 | become_user: "{{ deploy_user }}"
63 | shell: "{{ rbenv_ruby_configure_opts | default('') }} {{ rbenv_bin }} install {{ item }}"
64 | args:
65 | creates: "{{ rbenv_root_path }}/versions/{{ item }}"
66 | with_flattened:
67 | - "{{ additional_rubies }}"
68 | - "{{ ruby_version }}"
69 |
70 |
71 | - name: Check default ruby
72 | shell: '{{ rbenv_bin }} version | grep -oE "^[^ ]+"'
73 | changed_when: no
74 | register: rbenv_current_version
75 | become: yes
76 | become_user: "{{ deploy_user }}"
77 |
78 | - name: Set default ruby
79 | shell: "{{ rbenv_bin }} global {{ ruby_version }}"
80 | become: yes
81 | become_user: "{{ deploy_user }}"
82 | when: rbenv_current_version.stdout != ruby_version
83 |
84 | - name: Install Bundler
85 | shell: "{{ rbenv_root_path }}/shims/gem install bundler"
86 | become: yes
87 | become_user: "{{ deploy_user }}"
88 |
--------------------------------------------------------------------------------
/roles/sidekiq/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Copy sidekiq.service
3 | template:
4 | src: "{{ sidekiq_service_file }}"
5 | dest: /lib/systemd/system/sidekiq.service
6 | force: yes
7 | owner: "{{ deploy_user }}"
8 | group: "{{ deploy_group }}"
9 | mode: 0644
10 | register: sidekiq_service_file
11 |
12 | - name: Ensure that we re-read sidekiq.service
13 | systemd:
14 | daemon_reload: yes
15 | name: "sidekiq"
16 | when: sidekiq_service_file.changed
17 |
18 | - name: Enable sidekiq
19 | service:
20 | name: sidekiq
21 | enabled: yes
22 |
23 | - name: Restart sidekiq
24 | service:
25 | name: sidekiq
26 | state: restarted
27 |
--------------------------------------------------------------------------------
/roles/ssh/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ssh_port: 22
2 | ssh_password_authentication: "no"
3 | ssh_permit_root_login: "no"
4 | ssh_usedns: "no"
5 | ssh_permit_empty_password: "no"
6 | ssh_challenge_response_auth: "no"
7 | ssh_gss_api_authentication: "no"
8 | ssh_x11_forwarding: "no"
--------------------------------------------------------------------------------
/roles/ssh/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Restart SSH
3 | service:
4 | name: ssh
5 | state: restarted
--------------------------------------------------------------------------------
/roles/ssh/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Update SSH configuration to be more secure
3 | lineinfile:
4 | dest: "/etc/ssh/sshd_config"
5 | regexp: "{{ item.regexp }}"
6 | line: "{{ item.line }}"
7 | state: present
8 | with_items:
9 | - regexp: "^PasswordAuthentication"
10 | line: "PasswordAuthentication {{ ssh_password_authentication }}"
11 | - regexp: "^PermitRootLogin"
12 | line: "PermitRootLogin {{ ssh_permit_root_login }}"
13 | - regexp: "^Port"
14 | line: "Port {{ ssh_port }}"
15 | - regexp: "^UseDNS"
16 | line: "UseDNS {{ ssh_usedns }}"
17 | - regexp: "^PermitEmptyPasswords"
18 | line: "PermitEmptyPasswords {{ ssh_permit_empty_password }}"
19 | - regexp: "^ChallengeResponseAuthentication"
20 | line: "ChallengeResponseAuthentication {{ ssh_challenge_response_auth }}"
21 | - regexp: "^GSSAPIAuthentication"
22 | line: "GSSAPIAuthentication {{ ssh_gss_api_authentication }}"
23 | - regexp: "^X11Forwarding"
24 | line: "X11Forwarding {{ ssh_x11_forwarding }}"
25 | notify: Restart SSH
--------------------------------------------------------------------------------
/roles/ufw/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ufw_rules: []
--------------------------------------------------------------------------------
/roles/ufw/handlers/main.yml:
--------------------------------------------------------------------------------
1 | - name: Restart UFW
2 | service: name=ufw state=restarted
--------------------------------------------------------------------------------
/roles/ufw/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Install UFW
2 | apt: package=ufw state=present
3 |
4 | - name: Reset UFW to defaults
5 | ufw: state=reset
6 |
7 | - name: Configure UFW defaults
8 | ufw: direction={{ item.direction }} policy={{ item.policy }}
9 | with_items:
10 | - { direction: 'incoming', policy: 'deny' }
11 | - { direction: 'outgoing', policy: 'allow' }
12 | notify:
13 | - Restart UFW
14 |
15 | - name: Configure UFW rules
16 | ufw: rule={{ item.rule }} port={{ item.port }} from={{ item.from }} proto={{ item.proto }}
17 | with_items:
18 | - { rule: 'limit', port: '{{ ssh_port | default("22") }}', proto: 'tcp', from: "any" }
19 | - "{{ ufw_rules }}"
20 | notify:
21 | - Restart UFW
22 |
23 | - name: Enable UFW logging
24 | ufw: logging=on
25 | notify:
26 | - Restart UFW
27 |
28 | - name: Enable UFW
29 | ufw: state=enabled
--------------------------------------------------------------------------------
/roles/user/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | deploy_group: "{{ deploy_group }}"
3 | deploy_user: "{{ deploy_user }}"
4 | copy_local_key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"
--------------------------------------------------------------------------------
/roles/user/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Ensure sudo group is present
3 | group:
4 | name: sudo
5 | state: present
6 |
7 | - name: Ensure sudo group has sudo privileges
8 | lineinfile:
9 | dest: /etc/sudoers
10 | state: present
11 | regexp: "^%sudo"
12 | line: "%sudo ALL=(ALL:ALL) ALL"
13 | validate: "/usr/sbin/visudo -cf %s"
14 |
15 | - name: Make sure we have a deployment group
16 | group:
17 | name: "{{ deploy_group }}"
18 | state: present
19 |
20 | - name: Allow deployment group to have passwordless sudo
21 | lineinfile:
22 | path: /etc/sudoers
23 | state: present
24 | regexp: '^%{{ deploy_group }}'
25 | line: '%{{ deploy_group }} ALL=(ALL) NOPASSWD: ALL'
26 | validate: '/usr/sbin/visudo -cf %s'
27 |
28 | - name: Create a new user with sudo privileges
29 | user:
30 | name: "{{ deploy_user }}"
31 | state: present
32 | groups: "{{ deploy_group }}"
33 | append: true
34 | create_home: true
35 | shell: /bin/bash
36 |
37 | - name: Set authorized key for remote user
38 | authorized_key:
39 | user: "{{ deploy_user }}"
40 | state: present
41 | key: "{{ copy_local_key }}"
--------------------------------------------------------------------------------
/roles/yarn/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add Yarn apt key
3 | apt_key:
4 | url: https://dl.yarnpkg.com/debian/pubkey.gpg
5 |
6 | - name: Add Yarn repository
7 | apt_repository:
8 | repo: "deb https://dl.yarnpkg.com/debian/ stable main"
9 | filename: yarn
10 |
11 | - name: Install Yarn
12 | apt:
13 | name: yarn
--------------------------------------------------------------------------------
/templates/.rbenv-vars.j2:
--------------------------------------------------------------------------------
1 | RAILS_ENV=production
2 | RACK_ENV=production
3 | RAILS_MASTER_KEY={{ vault_rails_master_key }}
4 | DB_POOL={{ rails_db_pool }}
5 |
6 | # Puma
7 | APP_DIR={{ app_current_path }}
8 | SHARED_DIR={{ app_shared_path }}
9 | WEB_CONCURRENCY={{ puma_web_concurrency }}
--------------------------------------------------------------------------------
/templates/database.yml.j2:
--------------------------------------------------------------------------------
1 | production:
2 | adapter: postgresql
3 | encoding: unicode
4 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
5 | database: "{{ postgresql_db_name }}"
6 | username: "{{ postgresql_db_user }}"
7 | password: "{{ postgresql_db_password }}"
8 | host: "localhost"
--------------------------------------------------------------------------------
/templates/nginx.conf.j2:
--------------------------------------------------------------------------------
1 | upstream app {
2 | # Path to Puma SOCK file, as defined previously
3 | server unix:///{{ puma_socket }} fail_timeout=0;
4 | }
5 |
6 | server {
7 | listen 80;
8 | server_name localhost;
9 |
10 | root {{ app_current_path }}/public;
11 |
12 | try_files $uri/index.html $uri @app;
13 |
14 | location @app {
15 | proxy_pass http://app;
16 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
17 | proxy_set_header Host $http_host;
18 | proxy_redirect off;
19 | }
20 |
21 | error_page 500 502 503 504 /500.html;
22 | client_max_body_size 4G;
23 | keepalive_timeout 10;
24 | }
--------------------------------------------------------------------------------
/templates/puma.service.j2:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Puma HTTP Server
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | User={{ deploy_user }}
8 | EnvironmentFile={{ app_root_path }}/.rbenv-vars
9 |
10 | WorkingDirectory={{ app_current_path }}
11 |
12 | ExecStart={{ rbenv_bundle }} exec puma -C {{ app_current_path }}/config/puma.rb
13 | ExecStop={{ rbenv_bundle }} exec puma -S {{ app_current_path }}/config/puma.rb
14 | PIDFile={{ app_pids_path }}/puma.pid
15 |
16 | # Should systemd restart puma?
17 | # Use "no" (the default) to ensure no interference when using
18 | # stop/start/restart via `pumactl`. The "on-failure" setting might
19 | # work better for this purpose, but you must test it.
20 | # Use "always" if only `systemctl` is used for start/stop/restart, and
21 | # reconsider if you actually need the forking config.
22 | Restart=always
23 |
24 | [Install]
25 | WantedBy=multi-user.target
26 |
27 |
--------------------------------------------------------------------------------
/templates/sidekiq.service.j2:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Sidekiq
3 | After=network.target
4 |
5 | [Service]
6 | Type=notify
7 | User={{ deploy_user }}
8 |
9 | EnvironmentFile={{ app_root_path }}/.rbenv-vars
10 |
11 | WorkingDirectory={{ app_current_path }}
12 |
13 | ExecStart={{ rbenv_bundle }} exec sidekiq -e production -C config/sidekiq.yml
14 |
15 | # Greatly reduce Ruby memory fragmentation and heap usage
16 | # https://www.mikeperham.com/2018/04/25/taming-rails-memory-bloat/
17 | Environment=MALLOC_ARENA_MAX=2
18 |
19 | # if we crash, restart
20 | RestartSec=1
21 | Restart=on-failure
22 |
23 | [Install]
24 | WantedBy=multi-user.target
--------------------------------------------------------------------------------