Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add borg role and configurable SSH from API container #11

Merged
merged 3 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ansible-lint
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ exclude_paths:
skip_list:
- name[template]
- yaml[line-length]
- var-naming[no-role-prefix]
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ ungrouped:
ansible_user: <remote_username>
api_url: <desired_datalab_api_url>
app_url: <desired_datalab_app_url>
# Additional optional settings:
mount_data_disk: <disk device file location, e.g., /dev/sda, /dev/sdb or otherwise>
data_disk_type: <the fstype of the data disk, defaults to 'xfs'
borg_encryption_passphrase: <the passphrase for the borg encryption>
borg_remote_path: <the command to run borg on the repository (e.g., borg1 vs borg2)>
borg_repository: <the path to the borg repository, either local or remote>
```

where `<hostname>` and the various setting should be configured with your chosen
Expand All @@ -112,17 +118,19 @@ These files contain the desired *datalab* settings:
2. `./vaults/datalab/.env_server`: the secrets required by the server as an env
file (e.g., keys to external integration with GitHub, ORCID).
3. `./vaults/datalab/.env`: any variables required by the web app.
4. `./vaults/datalab/.ssh` (OPTIONAL): any SSH keys and config required to be mounted into the server container. These files should each be individually encrypted.

It is recommended that you version control these files **with encryption** and commit it to your
fork.
To encrypt them, you can run

```shell
ansible-vault encrypt inventory.yml vaults/datalab/prod_config.json vaults/datalab/.env vaults/datalab/.env_server
ansible-vault encrypt inventory.yml vaults/datalab/prod_config.json vaults/datalab/.env vaults/datalab/.env_server vaults/datalab/.ssh/*
```

and provide a password when prompted (which will then need to be kept safe and
used every time the Ansible playbook is run).
used every time the Ansible playbook is run). Omit the final SSH wildcard if no
SSH keyse are required.
You should never commit these files directly without encryption.

Once all these configuration steps have been performed, we can try to execute
Expand Down
3 changes: 3 additions & 0 deletions ansible/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ setup:
maintenance:
ansible-playbook -v -i inventory.yml --ask-vault-pass playbook.yml --tags="maintenance"

borg:
ansible-playbook -v -i inventory.yml --ask-vault-pass playbook.yml --tags="borg"

ssl:
ansible-playbook -v -i inventory.yml --ask-vault-pass playbook.yml --tags="ssl"
3 changes: 3 additions & 0 deletions ansible/inventory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ ungrouped:
app_url: <desired_datalab_app_url>
mount_data_disk: <disk device file location, e.g., /dev/sda, /dev/sdb or otherwise>
data_disk_type: <the fstype of the data disk, defaults to 'xfs'
borg_encryption_passphrase: <the passphrase for the borg encryption>
borg_remote_path: <the command to run borg on the repository (e.g., borg1 vs borg2)>
borg_repository: <the path to the borg repository, either local or remote>
3 changes: 3 additions & 0 deletions ansible/playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
- role: nginx
name: Launch nginx container with autorenewing certbot
tags: [setup, maintenance, ssl]
- role: borg
name: Configure borg(matic) and remote backups
tags: [borg, setup]

tasks:
- name: Keep all packages up-to-date
Expand Down
5 changes: 5 additions & 0 deletions ansible/requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ collections:
version: 1.5.4
- name: community.general
version: 8.2.0
- name: community.crypto
version: 2.21.1
roles:
- name: borgbase.ansible_role_borgbackup
version: 1.0.1
45 changes: 45 additions & 0 deletions ansible/roles/borg/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
borg_encryption_passphrase: ""
borg_exclude_patterns: []
borg_one_file_system: true
borg_exclude_from: []
borg_encryption_passcommand: false
borg_lock_wait_time: 5
borg_ssh_key_type: ed25519
borg_ssh_key_name: id_{{ borg_ssh_key_type }}
borg_ssh_key_file_path: "{{ backup_user_info.home }}/.ssh/{{ borg_ssh_key_name }}"
borg_ssh_command: false
borg_remote_path: false
borg_remote_rate_limit: 0
borg_retention_policy:
keep_hourly: 3
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
borg_version: false
borgmatic_timer_cron_name: borgmatic
borgmatic_timer: cron
borgmatic_timer_hour: "{{ range(0, 5) | random(seed=inventory_hostname) }}"
borgmatic_timer_minute: "{{ range(0, 59) | random(seed=inventory_hostname) }}"
borg_install_method: pip
borg_require_epel: "{{ ansible_os_family == 'RedHat' and ansible_distribution != 'Fedora' }}"

borgmatic_hooks:
on_error:
- echo "`date` - Error while creating a backup."
before_backup:
- echo "`date` - Starting backup."
after_backup:
- echo "`date` - Finished backup."
borgmatic_checks:
- name: repository
frequency: 4 weeks
- name: archives
frequency: 6 weeks
borgmatic_check_last: 3
borgmatic_store_atime: true
borgmatic_store_ctime: true
borgmatic_relocated_repo_access_is_ok: false
borgmatic_version: ">=1.7.11"

borg_venv_path: /opt/borgmatic
73 changes: 73 additions & 0 deletions ansible/roles/borg/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
- name: Install borg
become: true
ansible.builtin.apt:
name:
- borgbackup
state: present

- name: Install pipx
ansible.builtin.pip:
name: pipx
state: present

- name: Install borgmatic with pipx
community.general.pipx:
name: borgmatic ~= 1.8, < 2
state: present

- name: Install MongoDB tools
become: true
ansible.builtin.apt:
deb: https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.10.0.deb

- name: Ensure /etc/borgmatic exists
become: true
ansible.builtin.file:
path: /etc/borgmatic
state: directory
mode: "0700"
owner: "{{ ansible_ssh_user }}"

- name: Add Borgmatic configuration
become: true
ansible.builtin.template:
src: config.yaml.j2
dest: /etc/borgmatic/config.yaml
mode: "0600"
owner: "{{ ansible_ssh_user }}"

vars:
borg_exclude_patterns: []
borg_exclude_from: []
borg_install_method: package
borg_user: "{{ ansible_user }}"
borg_source_directories:
- /data
borgmatic_hooks:
before_backup:
- echo "`date` - Starting backup."
mongodb_databases:
- name: all
port: 27017
borgmatic_timer: cron
borg_retention_policy:
keep_daily: 30
keep_weekly: 0
keep_monthly: 12
keep_yearly: 4
borg_one_file_system: true
borgmatic_store_atime: true
borgmatic_store_ctime: true
borg_encryption_passcommand: false
borg_remote_rate_limit: 0
borg_ssh_command: ssh
borg_lock_wait_time: 5

- name: Add Cron job for borgmatic
ansible.builtin.cron:
name: borgmatic
hour: "2"
minute: "{{ range(0, 59) | random(seed=inventory_hostname) }}"
user: "{{ ansible_user }}"
job: /bin/bash -l -c 'source /home/{{ ansible_user }}/.profile && borgmatic -c /etc/borgmatic/config.yaml'
182 changes: 182 additions & 0 deletions ansible/roles/borg/templates/config.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#jinja2: lstrip_blocks: "True", trim_blocks: "True"
---
# Managed by Ansible, please don't edit manually

# Full config: https://torsion.org/borgmatic/docs/reference/config.yaml
location:
{% if borg_source_directories is not defined or borg_source_directories | length == 0 %}
source_directories:
- /etc/hostname # prevent empty backupconfig
{% else %}
source_directories:
{% for dir in borg_source_directories %}
- {{ dir }}
{% endfor %}
{% endif %}

# Stay in same file system (do not cross mount points).
one_file_system: {{ borg_one_file_system }}
repositories:
{% if borg_repository is iterable and (borg_repository is not string and borg_repository is not mapping) %}
{% for repo in borg_repository %}
- {{ repo }}
{% endfor %}
{% elif borg_repository is defined and borg_repository is string %}
- {{ borg_repository }}
{% endif %}

# Store atime into archive.
atime: {{ borgmatic_store_atime }}

# Store ctime into archive.
ctime: {{ borgmatic_store_ctime }}

{% if borg_exclude_patterns %}
# Any paths matching these patterns are excluded from backups. Globs and tildes
# are expanded. See the output of "borg help patterns" for more details.
exclude_patterns:
{% for dir in borg_exclude_patterns %}
- '{{ dir }}'
{% endfor %}
{% endif %}
{% if borg_exclude_from %}
# Read exclude patterns from one or more separate named files, one pattern per
# line. See the output of "borg help patterns" for more details.
exclude_from:
{% for dir in borg_exclude_from %}
- {{ dir }}
{% endfor %}
{% endif %}

# Exclude directories that contain a CACHEDIR.TAG file. See
# http://www.brynosaurus.com/cachedir/spec.html for details.
exclude_caches: true

# Exclude directories that contain a file with the given filename.
exclude_if_present: .nobackup

# Alternate Borg remote executable. Defaults to "borg".
# remote_path: borg1
{% if borg_remote_path %}
remote_path: {{ borg_remote_path }}
{% endif %}

# Repository storage options. See
# https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create and
# https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for
# details.
storage:
{% if borg_encryption_passphrase %}
encryption_passphrase: {{ borg_encryption_passphrase }}

{% endif %}
# The standard output of this command is used to unlock the encryption key. Only
# use on repositories that were initialized with passcommand/repokey encryption.
# Note that if both encryption_passcommand and encryption_passphrase are set,
# then encryption_passphrase takes precedence.
# encryption_passcommand: secret-tool lookup borg-repository repo-name
{% if borg_encryption_passcommand %}
encryption_passcommand: {{ borg_encryption_passcommand }}
{% endif %}

# Type of compression to use when creating archives. See
# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-create for details.
# Defaults to no compression.
compression: auto,zstd

# Remote network upload rate limit in kiBytes/second.
{% if borg_remote_rate_limit %}
remote_rate_limit: {{ borg_remote_rate_limit }}
{% endif %}

# Command to use instead of just "ssh". This can be used to specify ssh options.
# ssh_command: ssh -i ~/.ssh/id_ed25519
{% if borg_ssh_command %}
ssh_command: {{ borg_ssh_command }}
{% endif %}

# Umask to be used for borg create.
umask: 0077

# Maximum seconds to wait for acquiring a repository/cache lock.
lock_wait: {{ borg_lock_wait_time }}

# Name of the archive. Borg placeholders can be used. See the output of
# "borg help placeholders" for details. Default is
# "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this option, you must
# also specify a prefix in the retention section to avoid accidental pruning of
# archives with a different archive name format. And you should also specify a
# prefix in the consistency section as well.
archive_name_format: '{hostname}-{now:%Y-%m-%d-%H%M%S}'

# Bypass Borg error about a repository that has been moved.
relocated_repo_access_is_ok: {{ borgmatic_relocated_repo_access_is_ok }}

# Retention policy for how many backups to keep in each category. See
# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-prune for details.
# At least one of the "keep" options is required for pruning to work.
retention:
{% if borg_retention_policy.keep_secondly is defined %}
# Number of secondly archives to keep.
keep_secondly: {{ borg_retention_policy.keep_secondly }}
{% endif %}

{% if borg_retention_policy.keep_minutely is defined %}
# Number of minutely archives to keep.
keep_minutely: {{ borg_retention_policy.keep_minutely }}
{% endif %}

{% if borg_retention_policy.keep_hourly is defined %}
# Number of hourly archives to keep.
keep_hourly: {{ borg_retention_policy.keep_hourly }}
{% endif %}

{% if borg_retention_policy.keep_daily is defined %}
# Number of daily archives to keep.
keep_daily: {{ borg_retention_policy.keep_daily }}
{% endif %}

{% if borg_retention_policy.keep_weekly is defined %}
# Number of weekly archives to keep.
keep_weekly: {{ borg_retention_policy.keep_weekly }}
{% endif %}

{% if borg_retention_policy.keep_monthly is defined %}
# Number of monthly archives to keep.
keep_monthly: {{ borg_retention_policy.keep_monthly }}
{% endif %}

{% if borg_retention_policy.keep_yearly is defined %}
# Number of yearly archives to keep.
keep_yearly: {{ borg_retention_policy.keep_yearly }}
{% endif %}

# Consistency checks to run after backups. See
# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-check and
# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-extract for details.
consistency:
# List of one or more consistency checks to run: "repository",
# "archives", "data", and/or "extract". Defaults to
# "repository" and "archives". Set to "disabled" to disable
# all consistency checks. "repository" checks the consistency
# of the repository, "archives" checks all of the archives,
# "data" verifies the integrity of the data within the
# archives, and "extract" does an extraction dry-run of the
# most recent archive. Note that "data" implies "archives".
checks:
{% for checks in borgmatic_checks %}
- {{ checks }}
{% endfor %}

# Restrict the number of checked archives to the last n. Applies only to the "archives" check.
check_last: {{ borgmatic_check_last }}

# Shell commands or scripts to execute before and after a backup or if an error has occurred.
# IMPORTANT: All provided commands and scripts are executed with user permissions of borgmatic.
# Do not forget to set secure permissions on this file as well as on any script listed (chmod 0700) to
# prevent potential shell injection or privilege escalation.
hooks:
{% for hook in borgmatic_hooks %}
{{ hook }}:
{{ borgmatic_hooks[hook] | to_nice_yaml(indent=2) | trim | indent(8) }}
{% endfor %}
Loading
Loading