snapshot: add new role with 'snapper' option

btrbk will be next \^*^/

Also:
- detect the root filesystem in play with `ansible_mounts` instead
  of specifying it manually.
- dnscrypt: hardcode some privacy settings
This commit is contained in:
Hoang Nguyen 2023-06-16 00:00:00 +07:00
parent cb124aa08f
commit 40ac02c67e
Signed by: folliehiyuki
GPG Key ID: B0567C20730E9B11
16 changed files with 158 additions and 41 deletions

View File

@ -5,3 +5,6 @@ skip_list:
- package-latest
- fqcn[action-core]
- name[casing]
warn_list:
- var-naming[no-role-prefix]

13
TODO.md
View File

@ -1,13 +1,16 @@
# TODO
# Todo list
Stuff that are planned to be changed.
Stuff that are planned to be added/changed.
## Configuration
- [ ] /etc/security/access.conf
- [ ] Filesystem snapshot:
- [ ] snapper / btrbk (rootfs=btrfs)
- [ ] sanoid (rootfs=zfs)
- [ ] btrbk (rootfs=btrfs)
- [ ] sanoid / zrepl (rootfs=zfs)
- [ ] Filesystem backup (I don't have spare hard drives -_- so not supported for now):
- [ ] Local incremental backups (to spare disk)
- [ ] Remote backups
- [ ] incron
- [ ] bees
- [ ] kea as another option for dhcp client
@ -17,7 +20,7 @@ Stuff that are planned to be changed.
## Cosmetic
- [ ] Packer + Terraform / Pulumi (zfs + btrfs VMs) for testing the playbook
- [ ] Packer + Terraform / Pulumi (zfs + btrfs VMs) for testing the playbook (need <https://github.com/digitalocean/go-libvirt/issues/171> implemented first)
## Just in case I forget

View File

@ -29,8 +29,6 @@ dns_resolver: dnscrypt-proxy
repository: https://ftp.udx.icscoe.jp/Linux/alpine
rootfs: btrfs
username: follie
# Don't specify "seat" or "polkitd" group here
@ -61,9 +59,6 @@ dnscrypt:
- quad9-dnscrypt-ip4-filter-pri
- cloudflare-security
- cloudflare-security-ipv6
ephemeral_keys: true
tls_disable_session_tickets: true
tls_cipher_suite: [52392, 49199]
bootstrap_resolvers: [9.9.9.9:53, 1.1.1.1:53]
netprobe_address: 1.1.1.1:53
local_doh:
@ -139,6 +134,43 @@ earlyoom:
# auditd by default rotates its logfile when reaching file size limit
auditd_logrotate_daily: false
# Configuration for filesystem snapshot tools ─────────────────────────────────
# NOTE: these are examples to take note of available options
snapper:
- name: home
subvolume: /home
pre_post_cleanup:
enabled: true
number_cleanup:
enabled: false
timeline:
cleanup_enabled: true
min_age: 1800
hourly: 8
daily: 4
weekly: 2
monthly: 0
yearly: 0
- name: root
subvolume: /
pre_post_cleanup:
enabled: true
min_age: 900
number_cleanup:
enabled: true
min_age: 1800
limit: 10-30
limit_important: 10
timeline:
cleanup_enabled: false
btrbk:
sanoid:
zrepl:
# Secrets encrypted with ansible-vault ────────────────────────────────────────
password: '{{ vault_password }}'

View File

@ -6,7 +6,7 @@ initramfs_generator:
snapshot_tool: '{{ ["sanoid", "zrepl"] if rootfs == "zfs"
else ["snapper", "btrbk"] if rootfs == "btrfs"
else ["lvm"] }}'
else ["none"] }}'
# NOTE: Keep this in sync with `shell_mappings` in roles/user/defaults/main.yml
usershell:

View File

@ -171,12 +171,12 @@ cert_refresh_delay = 240
## This may improve privacy but can also have a significant impact on CPU usage
## Only enable if you don't have a lot of network load
dnscrypt_ephemeral_keys = {{ dnscrypt.ephemeral_keys | lower }}
dnscrypt_ephemeral_keys = true
## DoH: Disable TLS session tickets - increases privacy but also latency
tls_disable_session_tickets = {{ dnscrypt.tls_disable_session_tickets | lower }}
tls_disable_session_tickets = true
## DoH: Use a specific cipher suite instead of the server preference
@ -194,7 +194,7 @@ tls_disable_session_tickets = {{ dnscrypt.tls_disable_session_tickets | lower }}
## Keep tls_cipher_suite empty if you have issues fetching sources or
## connecting to some DoH servers. Google and Cloudflare are fine with it.
tls_cipher_suite = {{ dnscrypt.tls_cipher_suite }}
tls_cipher_suite = [52392, 49199]
## Bootstrap resolvers
@ -497,7 +497,7 @@ cert_key_file = '{{ ansible_hostname }}.pem'
[blocked_names]
{% if dnscrypt.adblock %}
{% if (dnscrypt.adblock | bool) %}
## Path to the file of blocking rules (absolute, or relative to the same directory as the config file)
blocked_names_file = '/etc/dnscrypt-proxy/blocked-names.txt'

View File

@ -7,7 +7,6 @@
fstype: tmpfs
opts: rw,nosuid,nodev,size=4G,mode=1777
state: present
when: rootfs != 'zfs'
# /run is mounted with exec by default
- name: fstab | Harden mount options for /run

View File

@ -1,6 +1,6 @@
---
- name: ntpd | Adjust ntpd service configuration
copy:
copy: # noqa: jinja[spacing]
content: >
NTPD_OPTS="-N
{%- for pool in ntp_opts.pools %} -p {{ pool }}{% endfor %}

View File

@ -0,0 +1,7 @@
---
- name: Create .snapshots subvolumes manually
debug:
msg: >
Please create .snapshots/ directories and corresponding mounted subvolumes
under {{ snapper | map(attribute='subvolume') | join(', ') }} targets
manually.

View File

@ -0,0 +1 @@
---

View File

@ -0,0 +1,3 @@
---
- name: snapshot | Setup {{ snapshot_tool }}
include_tasks: '{{ snapshot_tool }}.yml'

View File

@ -0,0 +1 @@
---

View File

@ -0,0 +1,26 @@
---
- name: snapper | Install snapper package
community.general.apk:
name: snapper
state: present
- name: snapper | Install config for each target
template:
src: snapper.j2
dest: /etc/snapper/configs/{{ item.name }}
mode: '600'
owner: root
group: root
loop: '{{ snapper }}'
- name: snapper | Install main snapper config
copy:
content: |
# List of snapper configurations.
SNAPPER_CONFIGS="{{ snapper | map(attribute='name') | join(' ') }}"
dest: /etc/snapper/snapper
mode: '644'
owner: root
group: root
notify:
- Create .snapshots subvolumes manually

View File

@ -0,0 +1 @@
---

View File

View File

@ -0,0 +1,41 @@
SUBVOLUME="{{ item.subvolume }}"
FSTYPE="btrfs"
# No QGROUP value (yet?) since it's currently troublesome
QGROUP=""
# these are the default settings
SPACE_LIMIT="0.5"
FREE_LIMIT="0.2"
# Allow a list of users/groups to operate on this config's snapshots
ALLOW_USERS=""
ALLOW_GROUPS=""
SYNC_ACL="no"
BACKGROUND_COMPARISON="yes"
# Only "none" or "gzip" (I wish it supported zstd ^-^)
COMPRESSION="gzip"
# Cleaup snapshots based on the number created
NUMBER_CLEANUP="{{ item.number_cleanup.enabled | ternary('yes', 'no') }}"
NUMBER_MIN_AGE="{{ item.number_cleanup.min_age is defined | ternary(item.number_cleanup.min_age, 1800) }}"
NUMBER_LIMIT="{{ item.number_cleanup.limit is defined | ternary(item.number_cleanup.limit, 50) }}"
NUMBER_LIMIT_IMPORTANT="{{ item.number_cleanup.limit_important is defined | ternary(item.number_cleanup.limit_important, 10) }}"
# Configure hourly snapshots
TIMELINE_CREATE="yes"
TIMELINE_CLEANUP="{{ item.timeline.cleanup_enabled | ternary('yes', 'no') }}"
TIMELINE_MIN_AGE="{{ item.timeline.min_age is defined | ternary(item.timeline.min_age, 1800) }}"
TIMELINE_LIMIT_HOURLY="{{ item.timeline.hourly is defined | ternary(item.timeline.hourly, 10) }}"
TIMELINE_LIMIT_DAILY="{{ item.timeline.daily is defined | ternary(item.timeline.daily, 10) }}"
TIMELINE_LIMIT_WEEKLY="{{ item.timeline.weekly is defined | ternary(item.timeline.weekly, 0) }}"
TIMELINE_LIMIT_MONTHLY="{{ item.timeline.monthly is defined | ternary(item.timeline.monthly, 10) }}"
TIMELINE_LIMIT_YEARLY="{{ item.timeline.yearly is defined | ternary(item.timeline.yearly, 10) }}"
# Pre-post snapshot pairs
EMPTY_PRE_POST_CLEANUP="{{ item.pre_post_cleanup.enabled | ternary('yes', 'no') }}"
EMPTY_PRE_POST_MIN_AGE="{{ item.pre_post_cleanup.min_age is defined | ternary(item.pre_post_cleanup.min_age, 1800) }}"

View File

@ -1,33 +1,30 @@
---
- name: Gathering facts
hosts: all
gather_facts: true
tags: always
- name: Sanity checks
hosts: all
tags: always
tasks:
- name: Check user ID
fail:
msg: This playbook should only be run as 'root'
when: ansible_real_user_id != 0
- name: Import list of accepted values for defined variables
include_vars:
name: accepted_values
file: ./requirements/accepted_variables.yml
- name: Check defined values of top-level variables
fail:
msg: 'Variable `{{ item }}` needs to be 1 of {{ accepted_values[item] }}'
when: not vars[item] in accepted_values[item]
loop: '{{ accepted_values | flatten }}'
- name: Setup the system
hosts: all
# Hard-coded variables that shouldn't be configured
gather_facts: true
vars:
# Determine the fstype of root filesystem
# PERF: a shorter version but requires `py3-jmespath`: '{{ ansible_mounts | json_query("[?mount == `/`].fstype") | first }}'
rootfs: '{{ ansible_mounts | selectattr("mount", "equalto", "/") | map(attribute="fstype") | first }}'
# elogind needs polkit to function
use_polkit: '{{ (seat_manager == "elogind") | ternary("True", polkit) }}'
pre_tasks:
- name: Sanity checks
tags: always
block:
- name: Check user ID
fail:
msg: This playbook should be run as 'root'
when: ansible_real_user_id != 0
- name: Import list of accepted values for custom variables
include_vars:
name: accepted_values
file: ./requirements/accepted_variables.yml
- name: Check defined values of top-level variables
fail:
msg: 'Variable `{{ item }}` needs to be 1 of {{ accepted_values[item] }}'
when: not vars[item] in accepted_values[item]
loop: '{{ accepted_values | flatten }}'
roles:
- role: essential
tags: essential
@ -61,6 +58,9 @@
tags: usbguard
- role: zram
tags: zram
- role: snapshot
tags: snapshot
when: snapshot_tool != 'none'
- role: earlyoom
tags: earlyoom
- role: user