From 922733dcde0b330a838fd2d0fbd0b09724d21da2 Mon Sep 17 00:00:00 2001 From: Hoang Nguyen Date: Fri, 16 Feb 2024 00:00:00 +0700 Subject: [PATCH] Merge sysconfig repo The other repo is not large enough to be standalone, so include it here as a single role. More changes: - Update onedark pallete (https://github.com/Binaryify/OneDark-Pro/blob/master/src/themes/data/oneDarkPro.ts) - Fix ansible-lint warnings --- .ansible-lint | 4 - .ansible-lint-ignore | 1 + .gitignore | 1 + .yamllint | 5 + LICENSE | 2 +- TODO.md | 49 +- ansible.cfg | 2 +- filter_plugins/system_filters.py | 48 ++ group_vars/all/dotfiles.yml | 100 +++ group_vars/all/fonts.yml | 18 - group_vars/all/options.yml | 53 -- group_vars/all/system.yml | 219 +++++ group_vars/all/theme.yml | 13 - group_vars/all/user.yml | 8 - inventory.toml | 2 + inventory.yml | 5 - playbooks/dotfiles.yml | 27 +- playbooks/extend.yml | 4 +- playbooks/system.yml | 26 + requirements/assert.yml | 77 ++ requirements/collections.yml | 1 + roles/chroot/defaults/main.yml | 4 - roles/chroot/tasks/main.yml | 28 - roles/chroot/templates/alpine-chroot.j2 | 29 - roles/chroot/templates/repositories.j2 | 6 - roles/config/tasks/fish.yml | 2 +- roles/config/tasks/mpv.yml | 4 +- roles/config/templates/alacritty/alacritty.j2 | 64 +- roles/config/templates/emacs/config.j2 | 2 +- roles/config/templates/foot/foot.j2 | 61 +- roles/config/templates/git/config.j2 | 2 +- roles/containers/defaults/main.yml | 2 +- roles/packages/tasks/gcloud.yml | 2 +- roles/system/defaults/main.yml | 105 +++ roles/system/files/acpi/acpid_lid_close | 2 + roles/system/files/acpi/handler.sh | 30 + roles/system/files/acpi/logind.conf | 18 + roles/system/files/auditd/auditd.conf | 25 + roles/system/files/auditd/cron | 4 + roles/system/files/cron/empty-tmp | 4 + roles/system/files/cron/fstrim | 8 + roles/system/files/dns/blocked-ips.txt | 27 + roles/system/files/dns/cloaking-rules.txt | 6 + .../dns/domains-blocklist-local-additions.txt | 33 + roles/system/files/dns/domains-blocklist.conf | 160 ++++ .../files/dns/update_dnscrypt_blocklist | 11 + .../files/networking/connman/connman.confd | 14 + .../system/files/networking/connman/main.conf | 8 + roles/system/files/networking/hosts | 8 + roles/system/files/networking/iwd/main.conf | 16 + roles/system/handlers/main.yml | 32 + roles/system/tasks/acpi.yml | 19 + roles/system/tasks/acpi/acpid.yml | 23 + roles/system/tasks/acpi/busybox.yml | 16 + roles/system/tasks/acpi/elogind.yml | 25 + roles/system/tasks/apparmor.yml | 26 + roles/system/tasks/auditd.yml | 33 + roles/system/tasks/cron.yml | 23 + roles/system/tasks/cron/busybox.yml | 9 + roles/system/tasks/cron/cronie.yml | 33 + roles/system/tasks/cron/fcron.yml | 43 + roles/system/tasks/cron/scrub.yml | 23 + roles/system/tasks/cron/trim.yml | 25 + roles/system/tasks/devd.yml | 50 ++ roles/system/tasks/dns.yml | 17 + roles/system/tasks/dns/dnscrypt-proxy.yml | 112 +++ roles/system/tasks/dns/unbound.yml | 111 +++ roles/system/tasks/earlyoom.yml | 20 + roles/system/tasks/essential.yml | 105 +++ roles/system/tasks/fstab.yml | 58 ++ roles/system/tasks/libvirt.yml | 34 + roles/system/tasks/main.jsonnet | 39 + roles/system/tasks/main.yml | 64 ++ roles/system/tasks/networking.yml | 47 ++ roles/system/tasks/networking/connman.yml | 47 ++ roles/system/tasks/networking/iwd.yml | 31 + roles/system/tasks/networking/networking.yml | 25 + roles/system/tasks/nftables.yml | 19 + roles/system/tasks/ntpd.yml | 10 + roles/system/tasks/ntpd/README.md | 27 + roles/system/tasks/ntpd/busybox.yml | 18 + roles/system/tasks/ntpd/chrony.yml | 32 + roles/system/tasks/ntpd/ntpsec.yml | 15 + roles/system/tasks/ntpd/openntpd.yml | 17 + roles/system/tasks/seat.yml | 46 ++ roles/system/tasks/snapshot.yml | 3 + roles/system/tasks/snapshot/btrbk.yml | 26 + roles/system/tasks/snapshot/sanoid.yml | 14 + roles/system/tasks/snapshot/snapper.yml | 26 + roles/system/tasks/snapshot/zrepl.yml | 1 + roles/system/tasks/tlp.yml | 27 + roles/system/tasks/usbguard.yml | 47 ++ roles/system/tasks/user.yml | 49 ++ roles/system/tasks/user/doas.yml | 17 + roles/system/tasks/user/please.yml | 49 ++ roles/system/tasks/user/sudo.yml | 23 + roles/system/tasks/zram.yml | 31 + roles/system/templates/auditd/audit.rules.j2 | 256 ++++++ roles/system/templates/devd/51-android.j2 | 2 + .../system/templates/devd/70-uinput-wheel.j2 | 1 + roles/system/templates/devd/70-xp-pen.j2 | 1 + roles/system/templates/devd/99-calibrate.j2 | 1 + roles/system/templates/dns/dnscrypt-proxy.j2 | 763 ++++++++++++++++++ roles/system/templates/dns/resolvconf.j2 | 10 + roles/system/templates/dns/unbound.j2 | 396 +++++++++ roles/system/templates/earlyoom/conf.j2 | 29 + roles/system/templates/essential/motd.j2 | 2 + .../templates/essential/repositories.j2 | 3 + .../networking/connman-service.config.j2 | 21 + .../system/templates/networking/interfaces.j2 | 31 + roles/system/templates/nftables/nftables.j2 | 178 ++++ roles/system/templates/ntpd/chrony.conf.j2 | 31 + roles/system/templates/ntpd/ntp.conf.j2 | 58 ++ roles/system/templates/ntpd/ntpd.conf.j2 | 17 + roles/system/templates/snapshot/btrbk.conf.j2 | 66 ++ .../system/templates/snapshot/sanoid.conf.j2 | 18 + roles/system/templates/snapshot/snapper.j2 | 41 + roles/system/templates/tlp/00-custom.j2 | 1 + .../templates/tlp/by_host/alpine-tp/config.j2 | 20 + roles/system/templates/zram/zram-start.j2 | 18 + roles/system/templates/zram/zram-stop.j2 | 9 + roles/system/templates/zram/zram.j2 | 1 + roles/theme/tasks/fonts/bobbers.yml | 2 +- roles/theme/tasks/fonts/iosevka.yml | 2 +- roles/theme/tasks/sound.yml | 4 +- themes/nord.yml | 19 - themes/onedark.yml | 34 +- 127 files changed, 4693 insertions(+), 279 deletions(-) create mode 100644 .yamllint create mode 100644 filter_plugins/system_filters.py create mode 100644 group_vars/all/dotfiles.yml delete mode 100644 group_vars/all/fonts.yml delete mode 100644 group_vars/all/options.yml create mode 100644 group_vars/all/system.yml delete mode 100644 group_vars/all/theme.yml delete mode 100644 group_vars/all/user.yml create mode 100644 inventory.toml delete mode 100644 inventory.yml create mode 100644 playbooks/system.yml delete mode 100644 roles/chroot/defaults/main.yml delete mode 100644 roles/chroot/tasks/main.yml delete mode 100644 roles/chroot/templates/alpine-chroot.j2 delete mode 100644 roles/chroot/templates/repositories.j2 create mode 100644 roles/system/defaults/main.yml create mode 100644 roles/system/files/acpi/acpid_lid_close create mode 100644 roles/system/files/acpi/handler.sh create mode 100644 roles/system/files/acpi/logind.conf create mode 100644 roles/system/files/auditd/auditd.conf create mode 100644 roles/system/files/auditd/cron create mode 100644 roles/system/files/cron/empty-tmp create mode 100644 roles/system/files/cron/fstrim create mode 100644 roles/system/files/dns/blocked-ips.txt create mode 100644 roles/system/files/dns/cloaking-rules.txt create mode 100644 roles/system/files/dns/domains-blocklist-local-additions.txt create mode 100644 roles/system/files/dns/domains-blocklist.conf create mode 100644 roles/system/files/dns/update_dnscrypt_blocklist create mode 100644 roles/system/files/networking/connman/connman.confd create mode 100644 roles/system/files/networking/connman/main.conf create mode 100644 roles/system/files/networking/hosts create mode 100644 roles/system/files/networking/iwd/main.conf create mode 100644 roles/system/handlers/main.yml create mode 100644 roles/system/tasks/acpi.yml create mode 100644 roles/system/tasks/acpi/acpid.yml create mode 100644 roles/system/tasks/acpi/busybox.yml create mode 100644 roles/system/tasks/acpi/elogind.yml create mode 100644 roles/system/tasks/apparmor.yml create mode 100644 roles/system/tasks/auditd.yml create mode 100644 roles/system/tasks/cron.yml create mode 100644 roles/system/tasks/cron/busybox.yml create mode 100644 roles/system/tasks/cron/cronie.yml create mode 100644 roles/system/tasks/cron/fcron.yml create mode 100644 roles/system/tasks/cron/scrub.yml create mode 100644 roles/system/tasks/cron/trim.yml create mode 100644 roles/system/tasks/devd.yml create mode 100644 roles/system/tasks/dns.yml create mode 100644 roles/system/tasks/dns/dnscrypt-proxy.yml create mode 100644 roles/system/tasks/dns/unbound.yml create mode 100644 roles/system/tasks/earlyoom.yml create mode 100644 roles/system/tasks/essential.yml create mode 100644 roles/system/tasks/fstab.yml create mode 100644 roles/system/tasks/libvirt.yml create mode 100644 roles/system/tasks/main.jsonnet create mode 100644 roles/system/tasks/main.yml create mode 100644 roles/system/tasks/networking.yml create mode 100644 roles/system/tasks/networking/connman.yml create mode 100644 roles/system/tasks/networking/iwd.yml create mode 100644 roles/system/tasks/networking/networking.yml create mode 100644 roles/system/tasks/nftables.yml create mode 100644 roles/system/tasks/ntpd.yml create mode 100644 roles/system/tasks/ntpd/README.md create mode 100644 roles/system/tasks/ntpd/busybox.yml create mode 100644 roles/system/tasks/ntpd/chrony.yml create mode 100644 roles/system/tasks/ntpd/ntpsec.yml create mode 100644 roles/system/tasks/ntpd/openntpd.yml create mode 100644 roles/system/tasks/seat.yml create mode 100644 roles/system/tasks/snapshot.yml create mode 100644 roles/system/tasks/snapshot/btrbk.yml create mode 100644 roles/system/tasks/snapshot/sanoid.yml create mode 100644 roles/system/tasks/snapshot/snapper.yml create mode 100644 roles/system/tasks/snapshot/zrepl.yml create mode 100644 roles/system/tasks/tlp.yml create mode 100644 roles/system/tasks/usbguard.yml create mode 100644 roles/system/tasks/user.yml create mode 100644 roles/system/tasks/user/doas.yml create mode 100644 roles/system/tasks/user/please.yml create mode 100644 roles/system/tasks/user/sudo.yml create mode 100644 roles/system/tasks/zram.yml create mode 100644 roles/system/templates/auditd/audit.rules.j2 create mode 100644 roles/system/templates/devd/51-android.j2 create mode 100644 roles/system/templates/devd/70-uinput-wheel.j2 create mode 100644 roles/system/templates/devd/70-xp-pen.j2 create mode 100644 roles/system/templates/devd/99-calibrate.j2 create mode 100644 roles/system/templates/dns/dnscrypt-proxy.j2 create mode 100644 roles/system/templates/dns/resolvconf.j2 create mode 100644 roles/system/templates/dns/unbound.j2 create mode 100644 roles/system/templates/earlyoom/conf.j2 create mode 100644 roles/system/templates/essential/motd.j2 create mode 100644 roles/system/templates/essential/repositories.j2 create mode 100644 roles/system/templates/networking/connman-service.config.j2 create mode 100644 roles/system/templates/networking/interfaces.j2 create mode 100644 roles/system/templates/nftables/nftables.j2 create mode 100644 roles/system/templates/ntpd/chrony.conf.j2 create mode 100644 roles/system/templates/ntpd/ntp.conf.j2 create mode 100644 roles/system/templates/ntpd/ntpd.conf.j2 create mode 100644 roles/system/templates/snapshot/btrbk.conf.j2 create mode 100644 roles/system/templates/snapshot/sanoid.conf.j2 create mode 100644 roles/system/templates/snapshot/snapper.j2 create mode 100644 roles/system/templates/tlp/00-custom.j2 create mode 100644 roles/system/templates/tlp/by_host/alpine-tp/config.j2 create mode 100644 roles/system/templates/zram/zram-start.j2 create mode 100644 roles/system/templates/zram/zram-stop.j2 create mode 100644 roles/system/templates/zram/zram.j2 diff --git a/.ansible-lint b/.ansible-lint index f02178c..3b5128d 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -11,7 +11,3 @@ warn_list: - name[template] - latest[git] - yaml[line-length] - - # NOTE: Use busybox here so that's fine - - command-instead-of-module - - risky-shell-pipe diff --git a/.ansible-lint-ignore b/.ansible-lint-ignore index 2f37113..48b900e 100644 --- a/.ansible-lint-ignore +++ b/.ansible-lint-ignore @@ -11,3 +11,4 @@ roles/packages/tasks/pnpm-packages.yml key-order[task] roles/packages/tasks/pip-packages.yml key-order[task] roles/theme/tasks/main.yml key-order[task] roles/scripts/tasks/curl.yml key-order[task] +roles/system/tasks/main.yml key-order[task] diff --git a/.gitignore b/.gitignore index 6073ca5..0bbecaa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /filter_plugins/__pycache__/ /.ropeproject/ /playbooks/dist-newstyle/ +/host_vars/ diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..6a6f380 --- /dev/null +++ b/.yamllint @@ -0,0 +1,5 @@ +--- +rules: + indentation: + spaces: 2 + indent-sequences: consistent diff --git a/LICENSE b/LICENSE index 8c60438..c3bd143 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2023 Hoang Nguyen +Copyright (c) 2021-2024 Hoang Nguyen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/TODO.md b/TODO.md index fc1b6de..f357bb5 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,50 @@ Plans for the future of this dotfiles. -## Software +## General + +- [ ] Add [GuixWL](https://guixwl) workflow + +## System + +### Configuration + +- [ ] /etc/security/access.conf +- [ ] Filesystem snapshot: + - [ ] zrepl (rootfs=zfs) +- [ ] Root on tmpfs +- [ ] 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 +- [ ] booster and dracut options for initramfs_generator +- [ ] `i915.enable_guc=3` (/etc/modprobe.d/kms.conf) +- [ ] turnstile as an alternative to pam-rundir/elogind + +### Cosmetic + +- [ ] Packer + Terraform / Pulumi (zfs + btrfs VMs) for testing the playbook + +### Just in case I forget + +- [ ] nftables with rootful podman () +- [ ] Write docs about AlpineLinux installation: + - [ ] BTRFS on LUKS (no encrypted /boot) / ZFS on root + - [ ] Bootloader configuration: + - [ ] limine / grub (BTRFS) + - [ ] gummyboot (a.k.a systemd-boot) / stubbyboot / direct efistub (ZFS) + - [ ] [APK post-commit hook](https://ptrcnull.me/posts/alpine-commit-hooks/) in the case of gummyboot, stubbyboot, efistub and limine + - [ ] EFI secure boot (also sign fwupd efi binary) + - [ ] Add EFI entries for EFI shell and fwupd + - [ ] Common kernel parameters: `init_on_free=1 page_alloc.shuffle=1 lockdown=integrity quiet` + - [ ] ZFS: `root=ZFS=rpool/ROOT/alpine` + - [ ] BTRFS: `modules=sd-mod,usb-storage,btrfs,nvme rootfstype=btrfs cryptroot=UUID=<...> cryptdm=alpine` + +## Dotfiles + +### Software - [ ] ~~[wayout](https://git.sr.ht/~proycon/wayout)~~ - [x] ~~Use [bubblewrap](https://github.com/containers/bubblewrap) for some applications~~ ==> replace with rootless [podman](https://podman.io) and [apptainer](https://apptainer.org) @@ -26,7 +69,7 @@ Plans for the future of this dotfiles. - [ ] vimiv-qt - [ ] mpd-mpris (as an alternative to mpDris2) -## Cosmetic +### Cosmetic - [ ] GTK/Icons/Cursor theme, Sarasa font bootstrapping - [ ] Waybar config file for River @@ -39,7 +82,7 @@ Plans for the future of this dotfiles. - [ ] Build podman container images with ansible-bender or stacker - [ ] Add `target-determinator` and `aspect-cli` to packages (or containers, with underlying `bazel`) -## Sandboxing +### Sandboxing - [ ] Dockerfile for [animdl](https://github.com/justfoolingaround/animdl) + mpv - [ ] podman rootless with dbus access (without ANONYMOUS authentication) + socket permissions (wayland / dbus/ pipewire / pulseaudio) in container (777 is no good) diff --git a/ansible.cfg b/ansible.cfg index 54b2b85..54202a3 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -5,7 +5,7 @@ display_skipped_hosts = False host_key_checking = False deprecation_warnings = True # Relocate directories -inventory = ./inventory.yml +inventory = ./inventory.toml filter_plugins = ./filter_plugins roles_path = ./roles # Make the targets follow XDG specs diff --git a/filter_plugins/system_filters.py b/filter_plugins/system_filters.py new file mode 100644 index 0000000..87ea3a2 --- /dev/null +++ b/filter_plugins/system_filters.py @@ -0,0 +1,48 @@ +from ansible.errors import AnsibleFilterTypeError +from ansible.module_utils.six import string_types, integer_types + + +# Ansible's "quote" filter is only applied to shell module + +def quote_single(str): + """ + Quote a string with single quote. + + Example: a string -> 'a string' + """ + if isinstance(str, string_types): + return "'" + str + "'" + else: + raise AnsibleFilterTypeError( + "|quote_single expects string, got %s instead." % type(str)) + +def quote_double(str): + """ + Quote a string with double quote. + + Example: a string -> "a string" + """ + if isinstance(str, string_types): + return '"' + str + '"' + else: + raise AnsibleFilterTypeError( + "|quote_double expects string, got %s instead." % type(str)) + +def random_hex(num): + """ + Generate a random hex number within the range [0, num) + + Example: random_hex(2**16 - 1) = 446c + """ + if isinstance(num, integer_types): + import random + return '{:x}'.format(random.randint(0, num)) + else: + raise AnsibleFilterTypeError( + "|random_hex expects integer, got %s instead." % type(num)) + +class FilterModule(object): + """Custom Ansible jinja2 filters for sysconfig playbook.""" + + def filters(self): + return {'quote_single': quote_single, 'quote_double': quote_double, 'random_hex': random_hex} diff --git a/group_vars/all/dotfiles.yml b/group_vars/all/dotfiles.yml new file mode 100644 index 0000000..5dd51c0 --- /dev/null +++ b/group_vars/all/dotfiles.yml @@ -0,0 +1,100 @@ +--- +# Choices of components ──────────────────────────────────────────────────────────── + +launcher: rofi + +wallpaper_engine: swww + +image_viewer: qimgv + +clipboard: cliphist + +screenshot: grim + +notification: dunst + +# TODO: mopidy backends orther than local files +music_daemon: mpd + +# 'openssh' --> openssh-client-default +# 'dropbear' --> dropbear-dbclient +ssh_client: openssh + +# NOTE: only OpenSSH can be used for SSH commit signing +gitcommit_sign_method: openpgp + +# List of terminal emulators, in precedence order +terminal_emulators: + - foot + - alacritty + +buildkit: + # Set to false to use OCI worker. All other options will be ignored + containerd_worker: true + # "k8s.io" for Kubernetes, "default" for normal containerd, "buildkit" for buildkit + containerd_namespace: default + # Use "native" on ZFS + containerd_snapshotter: overlayfs + +# RedHat recommends 0077 for a secure server +# 0027 is a good in-between for desktop usage (0022 is the default) +umask: '0027' + +# Leave jq behind! `jaq`, `gojq` and `yq` are here for the rescue +jq_binary: /usr/bin/gojq + +# XDG directory spec (need to be absolute paths here) +xdg_dir: + bin_home: '{{ ansible_user_dir }}/.local/bin' + cache_home: '{{ ansible_user_dir }}/.cache' + config_home: '{{ ansible_user_dir }}/.config' + data_home: '{{ ansible_user_dir }}/.local/share' + state_home: '{{ ansible_user_dir }}/.local/state' + + # This is not in XDG spec, but it's used here as a user-scoped /usr/libexec + libexec_dir: '{{ ansible_user_dir }}/.local/libexec' + +# Font settings ──────────────────────────────────────────────────────────── + +cjk_term_font: Sarasa Term J + +cjk_monospace_font: Sarasa Mono J + +cjk_sans_font: Sarasa Fixed J + +cjk_serif_font: Sarasa Fixed Slab J + +term_font: IosevkaTerm Nerd Font + +monospace_font: Iosevka + +sans_font: Iosevka Aile + +serif_font: Iosevka Etoile + +font_size: 13.5 + +# Theme settings ──────────────────────────────────────────────────────────── + +theme: nord + +gtk_theme: Nordic + +icon_theme: Papirus-Dark + +cursor_theme: Bibata-Modern-Ice + +cursor_size: 24 + +# Transparency of Waybar, launchers (wofi, rofi, fuzzle) and terminals +ui_opacity: 0.96 + +# User information ────────────────────────────────────────────────────────── + +user_fullname: Hoang Nguyen + +user_email: folliekazetani@protonmail.com + +user_agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0 + +user_gpg_signature: B0567C20730E9B11 diff --git a/group_vars/all/fonts.yml b/group_vars/all/fonts.yml deleted file mode 100644 index 4ba51a8..0000000 --- a/group_vars/all/fonts.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -cjk_term_font: Sarasa Term J - -cjk_monospace_font: Sarasa Mono J - -cjk_sans_font: Sarasa Fixed J - -cjk_serif_font: Sarasa Fixed Slab J - -term_font: IosevkaTerm Nerd Font - -monospace_font: Iosevka - -sans_font: Iosevka Aile - -serif_font: Iosevka Etoile - -font_size: 13.5 diff --git a/group_vars/all/options.yml b/group_vars/all/options.yml deleted file mode 100644 index 2705998..0000000 --- a/group_vars/all/options.yml +++ /dev/null @@ -1,53 +0,0 @@ ---- -launcher: rofi - -wallpaper_engine: swww - -image_viewer: qimgv - -clipboard: cliphist - -screenshot: grim - -notification: dunst - -# TODO: mopidy backends orther than local files -music_daemon: mpd - -# 'openssh' --> openssh-client-default -# 'dropbear' --> dropbear-dbclient -ssh_client: openssh - -# NOTE: only OpenSSH can be used for SSH commit signing -gitcommit_sign_method: openpgp - -# List of terminal emulators, in precedence order -terminal_emulators: - - foot - - alacritty - -buildkit: - # Set to false to use OCI worker. All other options will be ignored - containerd_worker: true - # "k8s.io" for Kubernetes, "default" for normal containerd, "buildkit" for buildkit - containerd_namespace: default - # Use "native" on ZFS - containerd_snapshotter: overlayfs - -# RedHat recommends 0077 for a secure server -# 0027 is a good in-between for desktop usage (0022 is the default) -umask: '0027' - -# Leave jq behind! `jaq`, `gojq` and `yq` are here for the rescue -jq_binary: /usr/bin/gojq - -# XDG directory spec (need to be absolute paths here) -xdg_dir: - bin_home: '{{ ansible_user_dir }}/.local/bin' - cache_home: '{{ ansible_user_dir }}/.cache' - config_home: '{{ ansible_user_dir }}/.config' - data_home: '{{ ansible_user_dir }}/.local/share' - state_home: '{{ ansible_user_dir }}/.local/state' - - # This is not in XDG spec, but it's used here as a user-scoped /usr/libexec - libexec_dir: '{{ ansible_user_dir }}/.local/libexec' diff --git a/group_vars/all/system.yml b/group_vars/all/system.yml new file mode 100644 index 0000000..2457441 --- /dev/null +++ b/group_vars/all/system.yml @@ -0,0 +1,219 @@ +--- +# Choices of components ──────────────────────────────────────────────────────────── +# NOTE: verified with `requirements/accepted_variables.yml`, so keep them as top-level + +snapshot_tool: btrbk + +initramfs_generator: mkinitfs + +usershell: fish + +seat_manager: seatd + +dhcp_client: udhcpc + +# acpid implementation to use when elogind is not present +acpid_daemon: busybox + +device_manager: udev + +crond_provider: cronie + +syslog_provider: busybox + +ntp_client: ntpsec + +dns_resolver: dnscrypt-proxy + +sudo_provider: doas + +# Configurations ─────────────────────────────────────────────────────────────────── + +repository: https://ftp.udx.icscoe.jp/Linux/alpine + +username: follie + +# Don't specify "seat" or "polkitd" group here +usergroups: [wheel, input, audio, video, libvirt, users, pipewire] + +# Commands the wheel group is allowed to run without password +nopasswd_commands: [halt, reboot, poweroff, pm-suspend, dhcp_release] + +# Public NTP pools: https://www.ntppool.org/en/use.html +# Public NTS-enabled servers: https://github.com/jauderho/nts-servers +ntp_opts: + # NOTE: peer option isn't available in ntpsec. + # Also, we are just the NTP client => no need to exchange time with anyone + pools: [] + servers: + - time.cloudflare.com + - ntpmon.dcs1.biz + - nts.netnod.se + - ntp.zeitgitter.net + - virginia.time.system76.com + - ntp3.fau.de + - gps.ntp.br + # include 'nts' option on each server directive (common NTP pools don't support NTS yet) + nts_enabled: true + +dnscrypt: + adblock: true + server_names: + - quad9-doh-ip4-port443-filter-pri + - quad9-doh-ip6-port443-filter-pri + - quad9-dnscrypt-ip4-filter-pri + - cloudflare-security + - cloudflare-security-ipv6 + bootstrap_resolvers: [9.9.9.9:53, 1.1.1.1:53] + netprobe_address: 1.1.1.1:53 + local_doh: + enabled: false + listen_addresses: [127.0.0.1:3012] + path: '/dns-query' + anonymized_dns: # not compatible with DoH and ODoH servers + enabled: false + routes: + - server_name: '*' + via: + - anon-tiarap + - anon-tiarap-ipv6 + - anon-cs-tokyo + - anon-cs-sk + +unbound_upstream_nameservers: + - 9.9.9.9@853#dns.quad9.net + - 149.112.112.112@853#dns.quad9.net + - 2620:fe::fe@853#dns.quad9.net + - 2620:fe::9@853#dns.quad9.net + - 1.1.1.1@853#cloudflare-dns.com + - 1.0.0.1@853#cloudflare-dns.com + - 2606:4700:4700::1111@853#cloudflare-dns.com + - 2606:4700:4700::1001@853#cloudflare-dns.com + +# Enable/Disable access to /sys/firmware/efi/efivars +disable_uefi_access: false + +# Should polkit be used +# NOTE: have no effect when seat_manager == 'elogind' +use_polkit: false + +# Should be a file name existed inside /usr/share/consolefonts/ +console_font: ter-h22b.psf.gz + +# 'virtlockd' and 'virtlogd' will always be started so don't list them here +libvirt_daemons: + - virtinterfaced + - virtnetworkd + - virtnodedevd + - virtqemud + - virtstoraged + - virtproxyd + +# Whether to use `iwd` or `eiwd` +iwd_without_dbus: false + +# RFC 7217: generate a stable IPv6 link-local address for SLAAC +# NOTE: this is the default for dhcpcd (slaac private), and `stable-privacy` flag doesn't appear in `ip a` in this case +ipv6_stable_privacy_addr: true + +# Public facing network interfaces to configured +# - ip4_addr, ip6_addr should include netmask (e.g. 192.168.1.10/24) +# - don't include wireless interfaces here as they should use dhcp with iwctl +# - udhcpc: https://wiki.alpinelinux.org/wiki/Configure_Networking +network_interfaces: + - name: eth0 + ip4_type: dhcp + ip6_type: auto + +# Punching holes on the machine +# 546/UDP (IPv6 link-local client) is hardcoded (opened) so don't specify it here +opened_ports: + tcp: [] + udp: [] + +# earlyoom kills processes on its own so make it optional +earlyoom: + set_priority: true + mem_min_percent: 5,2 + swap_min_percent: 10,5 + +# auditd by default rotates its logfile when reaching file size limit +auditd_logrotate_daily: false + +# Configuration for filesystem snapshot tools ───────────────────────────────── + +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 + +# NOTE: some caveats to reduce config complexity +# - use the same targets for all subvolumes in each volume definition +# - use the same global retention policy for snapshot/backup/archive +# - there's only 1 global ssh config, 1 global crontab +btrbk: + cron: + hourly: snapshot + daily: resume + options: + lockfile: /var/lock/btrbk.lock + logfile: /var/log/btrbk.log + syslog: cron + timestamp_format: long + snapshot: + min_age: 6h + policy: 16h 8d 4w 2m + volumes: + - path: /mnt/root + snapshot_dir: '@snapshots' + subvolumes: ['@home', '@'] + - path: /mnt/media + snapshot_dir: '@snapshots' + subvolumes: ['@'] + +# See /etc/sanoid/sanoid.defaults.conf file for all config options +sanoid: + templates: + production: + frequent_period: 30 + hourly: 16 + daily: 8 + weekly: 4 + monthly: 2 + autosnap: 'yes' + autoprune: 'yes' + datasets: + rpool/ALPINE/root: + use_template: production + rpool/ALPINE/home: + use_template: production + frequent_period: 15 + +zrepl: + +# Secrets encrypted with ansible-vault ──────────────────────────────────────── + +password: '{{ vault_password }}' diff --git a/group_vars/all/theme.yml b/group_vars/all/theme.yml deleted file mode 100644 index 5c0c994..0000000 --- a/group_vars/all/theme.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -theme: nord - -gtk_theme: Nordic - -icon_theme: Papirus-Dark - -cursor_theme: Bibata-Modern-Ice - -cursor_size: 24 - -# Transparency of Waybar, launchers (wofi, rofi, fuzzle) and terminals -ui_opacity: 0.96 diff --git a/group_vars/all/user.yml b/group_vars/all/user.yml deleted file mode 100644 index 9ce9383..0000000 --- a/group_vars/all/user.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -user_name: Hoang Nguyen - -user_email: folliekazetani@protonmail.com - -user_agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0 - -user_gpg_signature: B0567C20730E9B11 diff --git a/inventory.toml b/inventory.toml new file mode 100644 index 0000000..e9cc315 --- /dev/null +++ b/inventory.toml @@ -0,0 +1,2 @@ +[local.hosts] +localhost = { ansible_connection = "local" } diff --git a/inventory.yml b/inventory.yml deleted file mode 100644 index 644f4c9..0000000 --- a/inventory.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -local: - hosts: - localhost: - ansible_connection: local diff --git a/playbooks/dotfiles.yml b/playbooks/dotfiles.yml index 996c58f..489b5f8 100644 --- a/playbooks/dotfiles.yml +++ b/playbooks/dotfiles.yml @@ -1,26 +1,25 @@ --- -- name: Gather facts and do some checks +- name: Deploy user configuration hosts: all gather_facts: true - tags: always - tasks: - - name: Check defined variables - import_tasks: ../tasks/check_variables.yml - - name: Check other facts - import_tasks: ../tasks/check_other.yml - - name: Import color variables for {{ theme }} - include_vars: - name: colors - file: ../themes/{{ theme }}.yml - -- name: Deploy dotfiles - hosts: all # These variables shouldn't be changed vars: dmenu_flag: '{{ "-dmenu -i" if launcher == "rofi" else "-d -i -O default" if launcher == "wofi" else "-d" }}' font_size_px: '{{ (font_size * 96.0 / 72.0) | int }}' # 96px = 1in = 72pt --> e.g. 13.5pt = 18px + pre_tasks: + - name: Run pre-deploy checks + tags: always + block: + - name: Check defined variables + import_tasks: ../tasks/check_variables.yml + - name: Check other facts + import_tasks: ../tasks/check_other.yml + - name: Import color variables for {{ theme }} + include_vars: + name: colors + file: ../themes/{{ theme }}.yml roles: - role: scripts tags: scripts diff --git a/playbooks/extend.yml b/playbooks/extend.yml index 429b4f2..8663728 100644 --- a/playbooks/extend.yml +++ b/playbooks/extend.yml @@ -1,11 +1,9 @@ --- -# This playbook should be run in trunks with tags +# This playbook should be run in trunks using tags - name: Tasks unrelated to dotfiles management hosts: all gather_facts: true roles: - - role: chroot - tags: chroot - role: containers tags: containers - role: packages diff --git a/playbooks/system.yml b/playbooks/system.yml new file mode 100644 index 0000000..c4e2fa9 --- /dev/null +++ b/playbooks/system.yml @@ -0,0 +1,26 @@ +--- +- name: Setup the system + hosts: all + gather_facts: true + 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/assert.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: Get ZFS pool facts + community.general.zpool_facts: + tags: always + roles: + - role: system diff --git a/requirements/assert.yml b/requirements/assert.yml index c03d52c..2a96501 100644 --- a/requirements/assert.yml +++ b/requirements/assert.yml @@ -1,4 +1,6 @@ --- +# Dotfiles ──────────────────────────────────────────────────────────── + theme: - nord - onedark @@ -43,3 +45,78 @@ image_viewer: - qimgv - vimiv # the Qt version - imv + +# System ──────────────────────────────────────────────────────────── + +initramfs_generator: + - mkinitfs + - dracut + - booster + +snapshot_tool: + - btrbk + - sanoid + - snapper + - zrepl + +# NOTE: Keep this in sync with `shell_mappings` in roles/system/defaults/main.yml +usershell: + - ash + - bash + - dash + - fish + - hilbish + - ion-shell + - nushell + - sh + - xonsh + - yash + - zsh + +seat_manager: + - seatd + - elogind + +acpid_daemon: + - busybox + - acpid + +device_manager: + - mdev + - mdevd + - udev + +# udhcpc, dhclient, dhcpcd are configured with 'networking' service using ifupdown-ng, +# in that same priority (/usr/libexec/ifupdown-ng/dhcp) +dhcp_client: + - udhcpc # from busybox + - dhcpcd + - dhclient + - connman + - kea + +crond_provider: + - busybox + - cronie + - fcron + +syslog_provider: + - busybox + - logbookd + - rsyslog + - sysklogd + +ntp_client: + - ntpsec + - busybox + - openntpd + - chrony + +dns_resolver: + - dnscrypt-proxy + - unbound + +sudo_provider: + - doas + - please + - sudo diff --git a/requirements/collections.yml b/requirements/collections.yml index 59dabe7..6613f7d 100644 --- a/requirements/collections.yml +++ b/requirements/collections.yml @@ -1,5 +1,6 @@ --- collections: - name: ansible.posix + - name: community.crypto - name: community.general - name: containers.podman diff --git a/roles/chroot/defaults/main.yml b/roles/chroot/defaults/main.yml deleted file mode 100644 index 383718d..0000000 --- a/roles/chroot/defaults/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -chroot_dir: '{{ ansible_user_dir }}/Alpine-chroot' -apk_repository: - - https://dl-cdn.alpinelinux.org/alpine diff --git a/roles/chroot/tasks/main.yml b/roles/chroot/tasks/main.yml deleted file mode 100644 index 8136083..0000000 --- a/roles/chroot/tasks/main.yml +++ /dev/null @@ -1,28 +0,0 @@ ---- -- name: chroot | Setup an AlpineLinux chroot - become: true - block: - - name: chroot | Ensure the chroot directory exists - file: - path: '{{ chroot_dir }}' - state: directory - mode: '755' - - - name: chroot | Create the chroot - command: - cmd: '/sbin/apk -X {{ apk_repository }}/edge/main -U --allow-untrusted -p {{ chroot_dir }} --initdb add alpine-base' - creates: '{{ chroot_dir }}/bin/busybox' - - - name: chroot | Update the repository urls - template: - src: repositories.j2 - dest: '{{ chroot_dir }}/etc/apk/repositories' - owner: root - group: root - mode: '644' - -- name: chroot | Install a stupid script for chrooting - template: - src: alpine-chroot.j2 - dest: '{{ xdg_dir.bin_home }}/alpine-chroot' - mode: '755' diff --git a/roles/chroot/templates/alpine-chroot.j2 b/roles/chroot/templates/alpine-chroot.j2 deleted file mode 100644 index db92a76..0000000 --- a/roles/chroot/templates/alpine-chroot.j2 +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh - -# Super stupid and lazy way to chroot -# Borrow some stuff from https://github.com/alpinelinux/alpine-chroot-install - -chroot_dir="{{ chroot_dir }}" - -user='root' -if [ $# -ge 2 ] && [ "$1" = '-u' ]; then - user="$2"; shift 2 -fi - -[ "$(id -u)" -eq 0 ] || _sudo='doas' - -# Pre -$_sudo mount -t proc none "${chroot_dir}/proc" -$_sudo mount -o bind,ro /dev "${chroot_dir}/dev" -$_sudo mount -o bind,ro /sys "${chroot_dir}/sys" -$_sudo cp -L /etc/resolv.conf "${chroot_dir}/etc/" - -# chroot -$_sudo chroot "${chroot_dir}" /usr/bin/env -i su -l "$user" \ - sh -c ". /etc/profile; \"\$@\"" \ - -- "${@:-sh}" - -# Post -$_sudo umount -l "${chroot_dir}/dev" -$_sudo umount -l "${chroot_dir}/proc" -$_sudo umount -l "${chroot_dir}/sys" diff --git a/roles/chroot/templates/repositories.j2 b/roles/chroot/templates/repositories.j2 deleted file mode 100644 index 300d314..0000000 --- a/roles/chroot/templates/repositories.j2 +++ /dev/null @@ -1,6 +0,0 @@ -{% for repo in apk_repository %} -{{ repo }}/edge/main -{{ repo }}/edge/community -{{ repo }}/edge/testing - -{% endfor %} diff --git a/roles/config/tasks/fish.yml b/roles/config/tasks/fish.yml index 4b8192e..a21d395 100644 --- a/roles/config/tasks/fish.yml +++ b/roles/config/tasks/fish.yml @@ -62,7 +62,7 @@ dest: '{{ xdg_dir.config_home }}/fish/functions/fzf_key_bindings.fish' mode: '644' -- name: fish | Install fisher +- name: fish | Install fisher # noqa: risky-shell-pipe command-instead-of-module shell: /usr/bin/curl -sSL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher args: executable: /usr/bin/fish diff --git a/roles/config/tasks/mpv.yml b/roles/config/tasks/mpv.yml index f3a5fd2..74831f9 100644 --- a/roles/config/tasks/mpv.yml +++ b/roles/config/tasks/mpv.yml @@ -23,7 +23,7 @@ dest: /tmp/Anime4K.zip mode: '644' - - name: mpv | Unzip Anime4K shaders + - name: mpv | Unzip Anime4K shaders # noqa: command-instead-of-module command: unzip /tmp/Anime4K.zip -d ~/.config/mpv/shaders args: creates: '{{ xdg_dir.config_home }}/mpv/shaders/Anime4K_Clamp_Highlights.glsl' @@ -43,7 +43,7 @@ dest: /tmp/material-design-iconic-font.zip mode: '644' - - name: mpv | Unzip Material Design Iconic font + - name: mpv | Unzip Material Design Iconic font # noqa: command-instead-of-module command: unzip -j /tmp/material-design-iconic-font.zip fonts/Material-Design-Iconic-Font.ttf -d ~/.config/mpv/fonts args: creates: '{{ xdg_dir.config_home }}/mpv/fonts/Material-Design-Iconic-Font.ttf' diff --git a/roles/config/templates/alacritty/alacritty.j2 b/roles/config/templates/alacritty/alacritty.j2 index 4d5cc17..fdbf724 100644 --- a/roles/config/templates/alacritty/alacritty.j2 +++ b/roles/config/templates/alacritty/alacritty.j2 @@ -54,25 +54,57 @@ end = { foreground = "{{ colors.green }}", background = "{{ colors.background }} foreground = "{{ colors.background }}" background = "{{ colors.foreground }}" +{% if theme == 'nord' %} [colors.normal] -black = "{{ colors.color0 }}" -red = "{{ colors.color1 }}" -green = "{{ colors.color2 }}" -yellow = "{{ colors.color3 }}" -blue = "{{ colors.color4 }}" -magenta = "{{ colors.color5 }}" -cyan = "{{ colors.color6 }}" -white = "{{ colors.color7 }}" +black = "#3b4252" +red = "#bf616a" +green = "#a3be8c" +yellow = "#ebcb8b" +blue = "#81a1c1" +magenta = "#b48ead" +cyan = "#88c0d0" +white = "#e5e9f0" [colors.bright] -black = "{{ colors.color8 }}" -red = "{{ colors.color9 }}" -green = "{{ colors.color10 }}" -yellow = "{{ colors.color11 }}" -blue = "{{ colors.color12 }}" -magenta = "{{ colors.color13 }}" -cyan = "{{ colors.color14 }}" -white = "{{ colors.color15 }}" +black = "#4c566a" +red = "#bf616a" +green = "#a3be8c" +yellow = "#ebcb8b" +blue = "#81a1c1" +magenta = "#b48ead" +cyan = "#8fbcbb" +white = "#eceff4" + +[colors.dim] +black = "#373e4d" +red = "#94545d" +green = "#809575" +yellow = "#b29e75" +blue = "#68809a" +magenta = "#8c738c" +cyan = "#6d96a5" +white = "#aeb3bb" +{% elif theme == 'onedark' %} +[colors.normal] +black = "#282c34" +red = "#be5046" +green = "#98c379" +yellow = "#d19a66" +blue = "#61afef" +magenta = "#c678dd" +cyan = "#56b6c2" +white = "#abb2bf" + +[colors.bright] +black = "#5c6370" +red = "#e06c75" +green = "#98c379" +yellow = "#e5c07b" +blue = "#61afef" +magenta = "#c678dd" +cyan = "#56b6c2" +white = "#ffffff" +{% endif %} [bell] animation = "EaseOutExpo" diff --git a/roles/config/templates/emacs/config.j2 b/roles/config/templates/emacs/config.j2 index 16f7105..7c004fa 100644 --- a/roles/config/templates/emacs/config.j2 +++ b/roles/config/templates/emacs/config.j2 @@ -1,7 +1,7 @@ ;;; $DOOMDIR/config.el -*- lexical-binding: t; -*- ;; Some functionality uses this to identify you -(setq user-full-name "{{ user_name }}" +(setq user-full-name "{{ user_fullname }}" user-mail-address "{{ user_email }}") (setq-default diff --git a/roles/config/templates/foot/foot.j2 b/roles/config/templates/foot/foot.j2 index 63224f3..ace3d7f 100644 --- a/roles/config/templates/foot/foot.j2 +++ b/roles/config/templates/foot/foot.j2 @@ -35,20 +35,49 @@ alpha={{ ui_opacity }} foreground={{ colors.foreground | regex_replace('^#', '') }} background={{ colors.background | regex_replace('^#', '') }} -regular0={{ colors.color0 | regex_replace('^#', '') }} -regular1={{ colors.color1 | regex_replace('^#', '') }} -regular2={{ colors.color2 | regex_replace('^#', '') }} -regular3={{ colors.color3 | regex_replace('^#', '') }} -regular4={{ colors.color4 | regex_replace('^#', '') }} -regular5={{ colors.color5 | regex_replace('^#', '') }} -regular6={{ colors.color6 | regex_replace('^#', '') }} -regular7={{ colors.color7 | regex_replace('^#', '') }} +{% if theme == 'nord' %} +regular0=3b4252 +regular1=bf616a +regular2=a3be8c +regular3=ebcb8b +regular4=81a1c1 +regular5=b48ead +regular6=88c0d0 +regular7=e5e9f0 -bright0={{ colors.color8 | regex_replace('^#', '') }} -bright1={{ colors.color9 | regex_replace('^#', '') }} -bright2={{ colors.color10 | regex_replace('^#', '') }} -bright3={{ colors.color11 | regex_replace('^#', '') }} -bright4={{ colors.color12 | regex_replace('^#', '') }} -bright5={{ colors.color13 | regex_replace('^#', '') }} -bright6={{ colors.color14 | regex_replace('^#', '') }} -bright7={{ colors.color15 | regex_replace('^#', '') }} +bright0=4c566a +bright1=bf616a +bright2=a3be8c +bright3=ebcb8b +bright4=81a1c1 +bright5=b48ead +bright6=8fbcbb +bright7=eceff4 + +dim0=373e4d +dim1=94545d +dim2=809575 +dim3=b29e75 +dim4=68809a +dim5=8c738c +dim6=6d96a5 +dim7=aeb3bb +{% elif theme == 'onedark' %} +regular0=282c34 +regular1=be5046 +regular2=98c379 +regular3=d19a66 +regular4=61afef +regular5=c678dd +regular6=56b6c2 +regular7=abb2bf + +bright0=5c6370 +bright1=e06c75 +bright2=98c379 +bright3=e5c07b +bright4=61afef +bright5=c678dd +bright6=56b6c2 +bright7=ffffff +{% endif %} diff --git a/roles/config/templates/git/config.j2 b/roles/config/templates/git/config.j2 index 8d087cf..e985c92 100644 --- a/roles/config/templates/git/config.j2 +++ b/roles/config/templates/git/config.j2 @@ -5,7 +5,7 @@ sshCommand = dbclient -y {% endif %} [user] - name = {{ user_name }} + name = {{ user_fullname }} email = {{ user_email }} {% if ssh_client == 'openssh' and gitcommit_sign_method == 'ssh' %} signingKey = {{ user_ssh_key_path | default('~/.ssh/id_ed25519') }} diff --git a/roles/containers/defaults/main.yml b/roles/containers/defaults/main.yml index 9af29bf..f0c590d 100644 --- a/roles/containers/defaults/main.yml +++ b/roles/containers/defaults/main.yml @@ -1,7 +1,7 @@ --- ltex_ls_version: 16.0.0 -marksman_version: 2023-12-09 +marksman_version: '2023-12-09' translate_shell_version: 0.9.7.1 diff --git a/roles/packages/tasks/gcloud.yml b/roles/packages/tasks/gcloud.yml index 1365b02..c455122 100644 --- a/roles/packages/tasks/gcloud.yml +++ b/roles/packages/tasks/gcloud.yml @@ -16,7 +16,7 @@ path: '{{ gcloud.dir }}/google-cloud-sdk' state: absent -- name: gcloud | Extract downloaded release tarball +- name: gcloud | Extract downloaded release tarball # noqa: command-instead-of-module command: cmd: tar -xzf /tmp/gcloud.tar.gz chdir: '{{ gcloud.dir }}' diff --git a/roles/system/defaults/main.yml b/roles/system/defaults/main.yml new file mode 100644 index 0000000..93064b8 --- /dev/null +++ b/roles/system/defaults/main.yml @@ -0,0 +1,105 @@ +--- +# Lookup: https://dnscrypt.info/public-servers +# List of servers: +# - https://github.com/DNSCrypt/dnscrypt-resolvers +# - https://github.com/DNSCrypt/dnscrypt-proxy/wiki/DNS-server-sources + +dnscrypt_server_sources: + - name: public-resolvers + urls: + - https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md + - https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md + - https://dnsr.evilvibes.com/v3/public-resolvers.md + - https://cdn.staticaly.com/gh/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md + - https://ipv6.download.dnscrypt.info/resolvers-list/v3/public-resolvers.md + cache_file: /var/cache/dnscrypt-proxy/public-resolvers.md + minisign_key: RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3 + refresh_delay: 72 + prefix: '' + + - name: relays + urls: + - https://download.dnscrypt.info/resolvers-list/v3/relays.md + - https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md + - https://dnsr.evilvibes.com/v3/relays.md + - https://cdn.staticaly.com/gh/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md + cache_file: /var/cache/dnscrypt-proxy/relays.md + minisign_key: RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3 + refresh_delay: 72 + prefix: '' + + - name: odoh-servers + urls: + - https://download.dnscrypt.info/resolvers-list/v3/odoh-servers.md + - https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md + - https://dnsr.evilvibes.com/v3/odoh-servers.md + - https://cdn.staticaly.com/gh/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md + cache_file: /var/cache/dnscrypt-proxy/odoh-servers.md + minisign_key: RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3 + refresh_delay: 24 + prefix: '' + + - name: odoh-relays + urls: + - https://download.dnscrypt.info/resolvers-list/v3/odoh-relays.md + - https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md + - https://dnsr.evilvibes.com/v3/odoh-relays.md + - https://cdn.staticaly.com/gh/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md + cache_file: /var/cache/dnscrypt-proxy/odoh-relays.md + minisign_key: RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3 + refresh_delay: 24 + prefix: '' + + - name: opennic + urls: + - https://download.dnscrypt.info/dnscrypt-resolvers/v3/opennic.md + - https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/opennic.md + - https://dnsr.evilvibes.com/v3/opennic.md + - https://cdn.staticaly.com/gh/DNSCrypt/dnscrypt-resolvers/master/v3/opennic.md + cache_file: /var/cache/dnscrypt-proxy/opennic.md + minisign_key: RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3 + refresh_delay: 72 + prefix: '' + + # - name: quad9-resolvers + # urls: + # - https://www.quad9.net/quad9-resolvers.md + # - https://raw.githubusercontent.com/Quad9DNS/dnscrypt-settings/main/dnscrypt/quad9-resolvers.md + # - https://quad9.net/dnscrypt/quad9-resolvers.md + # cache_file: /var/cache/dnscrypt-proxy/quad9.md + # minisign_key: RWQBphd2+f6eiAqBsvDZEBXBGHQBJfeG6G+wJPPKxCZMoEQYpmoysKUN + # refresh_delay: 72 + # prefix: 'quad9-' + + # - name: onion-services + # urls: + # - https://download.dnscrypt.info/dnscrypt-resolvers/v3/onion-services.md + # - https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/onion-services.md + # - https://dnsr.evilvibes.com/v3/onion-services.md + # - https://cdn.staticaly.com/gh/DNSCrypt/dnscrypt-resolvers/master/v3/onion-services.md + # cache_file: /var/cache/dnscrypt-proxy/onion-services.md + # minisign_key: RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3 + # refresh_delay: 72 + # prefix: '' + +libvirt_daemons_conf: + - unix_sock_group = "libvirt" + - unix_sock_ro_perms = "0770" + - unix_sock_rw_perms = "0770" + - auth_unix_ro = "none" + - auth_unix_rw = "none" + +shells_mappings: + ash: /bin/ash + bash: /bin/bash + dash: /usr/bin/dash + fish: /usr/bin/fish + hilbish: /usr/bin/hilbish + ion-shell: /usr/bin/ion + nushell: /usr/bin/nu + sh: /bin/sh + xonsh: /usr/bin/xonsh + yash: /usr/bin/yash + zsh: /bin/zsh + +zram_num_devices: 3 diff --git a/roles/system/files/acpi/acpid_lid_close b/roles/system/files/acpi/acpid_lid_close new file mode 100644 index 0000000..c6aadbc --- /dev/null +++ b/roles/system/files/acpi/acpid_lid_close @@ -0,0 +1,2 @@ +#!/bin/sh +echo mem > /sys/power/state diff --git a/roles/system/files/acpi/handler.sh b/roles/system/files/acpi/handler.sh new file mode 100644 index 0000000..b3f88c8 --- /dev/null +++ b/roles/system/files/acpi/handler.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +PATH="/usr/share/acpid:$PATH" +alias log='logger -t acpid' + +# ::: +case "$1:$2:$3:$4" in + +button/power:PWRF:* | button/power:PBTN:*) + log 'Power button pressed' + poweroff +;; +button/sleep:SLPB:*) + log 'Sleep button pressed' + # Suspend to RAM. + echo mem > /sys/power/state +;; +button/lid:*:close:*) + log 'Lid closed' + # Suspend to RAM if AC adapter is not connected. + [ "$(power-supply -d adapter -s)" = "offline" ] && echo mem > /sys/power/state +;; +ac_adapter:*:*:*0) + log 'AC adapter unplugged' + # Suspend to RAM if notebook's lid is closed. + lid-state -c && echo mem > /sys/power/state +;; +esac + +exit 0 diff --git a/roles/system/files/acpi/logind.conf b/roles/system/files/acpi/logind.conf new file mode 100644 index 0000000..b3c1635 --- /dev/null +++ b/roles/system/files/acpi/logind.conf @@ -0,0 +1,18 @@ +# See logind.conf(5) for details. + +[Login] +HandlePowerKey=poweroff +HandleSuspendKey=suspend +HandleLidSwitch=suspend +HandleLidSwitchExternalPower=suspend +PowerKeyIgnoreInhibited=no +SuspendKeyIgnoreInhibited=no +LidSwitchIgnoreInhibited=yes +HoldoffTimeoutSec=30s +IdleActionSec=30min + +[Sleep] +AllowSuspend=yes +AllowPowerOffInterrupts=no +AllowSuspendInterrupts=no +SuspendState=mem standby freeze diff --git a/roles/system/files/auditd/auditd.conf b/roles/system/files/auditd/auditd.conf new file mode 100644 index 0000000..e5548d1 --- /dev/null +++ b/roles/system/files/auditd/auditd.conf @@ -0,0 +1,25 @@ +# /etc/audit/auditd.conf +# See auditd.conf(5) + +local_events = yes +log_file = /var/log/audit/audit.log +write_logs = yes +log_format = ENRICHED +log_group = root +flush = INCREMENTAL_ASYNC +freq = 50 +num_logs = 7 +name_format = NONE +max_log_file = 8 +max_log_file_action = ROTATE +verify_email = yes +space_left = 100 +space_left_action = SYSLOG +admin_space_left = 75 +admin_space_left_action = SUSPEND +disk_full_action = SUSPEND +disk_error_action = SUSPEND +overflow_action = SYSLOG +max_restarts = 5 +plugin_dir = /etc/audit/plugins.d +end_of_event_timeout = 2 diff --git a/roles/system/files/auditd/cron b/roles/system/files/auditd/cron new file mode 100644 index 0000000..d199ed3 --- /dev/null +++ b/roles/system/files/auditd/cron @@ -0,0 +1,4 @@ +#!/bin/sh + +# auditd rotates its logfile on receiving SIGUSR1 +/usr/bin/pkill -USR1 -x /usr/sbin/auditd diff --git a/roles/system/files/cron/empty-tmp b/roles/system/files/cron/empty-tmp new file mode 100644 index 0000000..32e2341 --- /dev/null +++ b/roles/system/files/cron/empty-tmp @@ -0,0 +1,4 @@ +#!/bin/sh + +/usr/bin/find /var/tmp -type d -mindepth 1 -ctime +7 -exec rm -rf {} \; +/usr/bin/find /var/tmp -mindepth 1 -ctime +7 -exec rm -rf {} \; diff --git a/roles/system/files/cron/fstrim b/roles/system/files/cron/fstrim new file mode 100644 index 0000000..bb61d3e --- /dev/null +++ b/roles/system/files/cron/fstrim @@ -0,0 +1,8 @@ +#!/bin/sh + +# NOTE: we only trim things inside /etc/fstab to avoid ZFS mount paths +# (they will be handled by `zpool trim` cron job) + +# This needs fstrim from util-linux package. +# For busybox's fstrim, use multiple 'fstrim /mount_point'. +exec /sbin/fstrim -A diff --git a/roles/system/files/dns/blocked-ips.txt b/roles/system/files/dns/blocked-ips.txt new file mode 100644 index 0000000..660d443 --- /dev/null +++ b/roles/system/files/dns/blocked-ips.txt @@ -0,0 +1,27 @@ +############################## +# IP blocklist # +############################## + +# Localhost rebinding protection +0.0.0.0 +127.0.0.* + +# RFC1918 rebinding protection +10.* +172.16.* +172.17.* +172.18.* +172.19.* +172.20.* +172.21.* +172.22.* +172.23.* +172.24.* +172.25.* +172.26.* +172.27.* +172.28.* +172.29.* +172.30.* +172.31.* +192.168.* diff --git a/roles/system/files/dns/cloaking-rules.txt b/roles/system/files/dns/cloaking-rules.txt new file mode 100644 index 0000000..0ba7135 --- /dev/null +++ b/roles/system/files/dns/cloaking-rules.txt @@ -0,0 +1,6 @@ +################################ +# Cloaking rules # +################################ + +localhost 127.0.0.1 +localhost ::1 diff --git a/roles/system/files/dns/domains-blocklist-local-additions.txt b/roles/system/files/dns/domains-blocklist-local-additions.txt new file mode 100644 index 0000000..648d1a7 --- /dev/null +++ b/roles/system/files/dns/domains-blocklist-local-additions.txt @@ -0,0 +1,33 @@ +# Local set of patterns to block + +ad.* +ads.* +banner.* +banners.* +creatives.* +oas.* +oascentral.* +tag.* +telemetry.* +tracker.* + +# eth0.me is hardcoded in tools such as Archey, but is not available any +# more, causing issues such as terminal sessions taking a long time to +# start. + +eth0.me + +# ibpxl.com is a tracker that seems to frequently have issues, causing +# page loads to stall. + +ibpxl.com + +# ditto for that one + +internetbrands.com + +# Ubuntu's motd script sends way too much information to Canonical + +motd.ubuntu.com + +*.zip diff --git a/roles/system/files/dns/domains-blocklist.conf b/roles/system/files/dns/domains-blocklist.conf new file mode 100644 index 0000000..403c35e --- /dev/null +++ b/roles/system/files/dns/domains-blocklist.conf @@ -0,0 +1,160 @@ +################################################################################## +# # +# Generate a block list of domains using public data sources, and the local # +# domains-blocklist-local-additions.txt file. # +# # +# The default configuration is just indicative, and corresponds to the one # +# used to produce the public "mybase" set. # +# # +# Comment out the URLs of the sources you wish to disable, leave the ones # +# you would like enabled uncommented. Then run the script to build the # +# dnscrypt-blocklist-domains.txt file: # +# # +# $ generate-domains-blocklist.py -o dnscrypt-blocklist-domains.txt # +# # +# Domains that should never be blocked can be put into a file named # +# domains-allowlist.txt. # +# # +# That blocklist file can then be used in the dnscrypt-proxy.toml file: # +# # +# [blocked_names] # +# # +# blocked_names_file = 'dnscrypt-blocklist-domains.txt' # +# # +################################################################################## + +# Local additions +file:/etc/dnscrypt-proxy/adblock/domains-blocklist-local-additions.txt + +# AdAway is an open source ad blocker for Android using the hosts file. +# https://raw.githubusercontent.com/AdAway/adaway.github.io/master/hosts.txt + +# EasyList +https://easylist-downloads.adblockplus.org/easylist_noelemhide.txt + +# EasyList China +# https://easylist-downloads.adblockplus.org/easylistchina.txt + +# RU AdList +# https://easylist-downloads.adblockplus.org/advblock.txt + +# Peter Lowe's Ad and tracking server list +https://pgl.yoyo.org/adservers/serverlist.php?hostformat=nohtml + +# Spam404 +https://raw.githubusercontent.com/Spam404/lists/master/main-blacklist.txt + +# Malvertising filter list by Disconnect +# https://s3.amazonaws.com/lists.disconnect.me/simple_malvertising.txt + +# Ads filter list by Disconnect +# https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt + +# Basic tracking list by Disconnect +# https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt + +# KAD host file (fraud/adware) +# https://raw.githubusercontent.com/PolishFiltersTeam/KADhosts/master/KADomains.txt + +# BarbBlock list (spurious and invalid DMCA takedowns) +https://paulgb.github.io/BarbBlock/blacklists/domain-list.txt + +# Dan Pollock's hosts list +https://someonewhocares.org/hosts/hosts + +# NoTracking's list - blocking ads, trackers and other online garbage +https://raw.githubusercontent.com/notracking/hosts-blocklists/master/dnscrypt-proxy/dnscrypt-proxy.blacklist.txt + +# NextDNS CNAME cloaking list +https://raw.githubusercontent.com/nextdns/cname-cloaking-blocklist/master/domains + +# AdGuard Simplified Domain Names filter +https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + +# Geoffrey Frogeye's block list of first-party trackers - https://hostfiles.frogeye.fr/ +https://hostfiles.frogeye.fr/firstparty-trackers.txt + +# CoinBlockerLists: blocks websites serving cryptocurrency miners - https://gitlab.com/ZeroDot1/CoinBlockerLists/ - Contains false positives +# https://gitlab.com/ZeroDot1/CoinBlockerLists/raw/master/list_browser.txt + +# Websites potentially publishing fake news +# https://raw.githubusercontent.com/marktron/fakenews/master/fakenews + +# Quidsup NoTrack Blocklist - Contains too many false positives to be enabled by default +# https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt + +# Quidsup Malware Blocklist - Contains too many false positives to be enabled by default +# https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-malware.txt + +# AntiSocial Blacklist is an extensive collection of potentially malicious domains +# https://theantisocialengineer.com/AntiSocial_Blacklist_Community_V1.txt + +# Steven Black hosts file +https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts + +# A list of adserving and tracking sites maintained by @lightswitch05 +https://www.github.developerdan.com/hosts/lists/ads-and-tracking-extended.txt + +# A list of adserving and tracking sites maintained by @anudeepND +https://raw.githubusercontent.com/anudeepND/blacklist/master/adservers.txt + +# Anudeep's Blacklist (CoinMiner) - Blocks cryptojacking sites +# https://raw.githubusercontent.com/anudeepND/blacklist/master/CoinMiner.txt + +### Spark < Blu Go < Blu < Basic < Ultimate +### (With pornware blocking) Porn < Unified +# Energized Ultimate +# https://block.energized.pro/ultimate/formats/domains.txt + +# Energized Basic +# https://block.energized.pro/basic/formats/domains.txt + +# Energized BLU +# https://block.energized.pro/blu/formats/domains.txt + +# OISD.NL - Blocks ads, phishing, malware, tracking and more. WARNING: this is a huge list. +# https://dblw.oisd.nl/ + +# OISD.NL (smaller subset) - Blocks ads, phishing, malware, tracking and more. Tries to miminize false positives. +https://dblw.oisd.nl/basic/ + +# OISD.NL (extra) - Blocks ads, phishing, malware, tracking and more. Protection over functionality. +# https://dblw.oisd.nl/extra/ + +# Captain Miao ad list - Block ads and trackers, especially Chinese and Android trackers +# https://raw.githubusercontent.com/jdlingyu/ad-wars/master/sha_ad_hosts + +# Phishing Army - https://phishing.army/ +# https://phishing.army/download/phishing_army_blocklist.txt + +# Block pornography +# https://raw.githubusercontent.com/Clefspeare13/pornhosts/master/0.0.0.0/hosts +# https://raw.githubusercontent.com/Sinfonietta/hostfiles/master/pornography-hosts +# https://raw.githubusercontent.com/cbuijs/shallalist/master/porn/domains +# https://raw.githubusercontent.com/olbat/ut1-blacklists/master/blacklists/adult/domains +# https://block.energized.pro/porn/formats/domains.txt +# https://raw.githubusercontent.com/mhxion/pornaway/master/hosts/porn_sites.txt +# https://dblw.oisd.nl/nsfw/ + +# Block gambling sites +# https://raw.githubusercontent.com/Sinfonietta/hostfiles/master/gambling-hosts +# https://raw.githubusercontent.com/olbat/ut1-blacklists/master/blacklists/gambling/domains + +# Block dating websites +# https://raw.githubusercontent.com/olbat/ut1-blacklists/master/blacklists/dating/domains +# https://www.github.developerdan.com/hosts/lists/dating-services-extended.txt + +# Block social media sites +# https://raw.githubusercontent.com/Sinfonietta/hostfiles/master/social-hosts +# https://block.energized.pro/extensions/social/formats/domains.txt +# https://raw.githubusercontent.com/olbat/ut1-blacklists/master/blacklists/social_networks/domains +# https://www.github.developerdan.com/hosts/lists/facebook-extended.txt + +# Goodbye Ads - Specially designed for mobile ad protection +# https://raw.githubusercontent.com/jerryn70/GoodbyeAds/master/Hosts/GoodbyeAds.txt + +# NextDNS BitTorrent blocklist +# https://raw.githubusercontent.com/nextdns/bittorrent-blocklist/master/domains + +# Block spying and tracking on Windows +# https://raw.githubusercontent.com/crazy-max/WindowsSpyBlocker/master/data/dnscrypt/spy.txt diff --git a/roles/system/files/dns/update_dnscrypt_blocklist b/roles/system/files/dns/update_dnscrypt_blocklist new file mode 100644 index 0000000..4e99d10 --- /dev/null +++ b/roles/system/files/dns/update_dnscrypt_blocklist @@ -0,0 +1,11 @@ +#!/bin/sh + +dnscrypt_dir="/etc/dnscrypt-proxy" +adblock_dir="${dnscrypt_dir}/adblock" + +/usr/bin/python3 "${adblock_dir}"/generate-domains-blocklist.py \ + -i -a /dev/null -r /dev/null \ + -c "${adblock_dir}"/domains-blocklist.conf \ + -o "${dnscrypt_dir}"/blocked-names.txt + +/bin/chmod 0644 "${dnscrypt_dir}"/blocked-names.txt diff --git a/roles/system/files/networking/connman/connman.confd b/roles/system/files/networking/connman/connman.confd new file mode 100644 index 0000000..e15321b --- /dev/null +++ b/roles/system/files/networking/connman/connman.confd @@ -0,0 +1,14 @@ +# Configuration file for /etc/init.d/connman + +# Path to ConnMan's configuration file. +#cfgfile=/etc/connman/main.conf + +# Additional arguments to pass to connmand. +command_args="--nodnsproxy" + +# Number of milliseconds to wait after starting and check that daemon is +# still running. +#start_wait=50 + +# Uncomment to use process supervisor. +supervisor="supervise-daemon" diff --git a/roles/system/files/networking/connman/main.conf b/roles/system/files/networking/connman/main.conf new file mode 100644 index 0000000..b75c588 --- /dev/null +++ b/roles/system/files/networking/connman/main.conf @@ -0,0 +1,8 @@ +[General] +AllowHostnameUpdates = false +AllowDomainnameUpdates = false +PreferredTechnologies = ethernet,wifi +SingleConnectedTechnology = true +NetworkInterfaceBlacklist = vmnet,vboxnet,virbr,ifb,docker,veth,vnet,wlan,lxdbr,tap +EnableOnlineCheck = false +# AddressConflictDetection = true diff --git a/roles/system/files/networking/hosts b/roles/system/files/networking/hosts new file mode 100644 index 0000000..e9f16b4 --- /dev/null +++ b/roles/system/files/networking/hosts @@ -0,0 +1,8 @@ +127.0.0.1 localhost localhost.localdomain + +::1 ipv6-localhost ipv6-loopback +fe00::0 ipv6-localnet +ff00::0 ipv6-mcastprefix +ff02::1 ipv6-allnodes +ff02::2 ipv6-allrouters +ff02::3 ipv6-allhosts diff --git a/roles/system/files/networking/iwd/main.conf b/roles/system/files/networking/iwd/main.conf new file mode 100644 index 0000000..9267733 --- /dev/null +++ b/roles/system/files/networking/iwd/main.conf @@ -0,0 +1,16 @@ +# See iwd.config(5) for all options + +[General] +EnableNetworkConfiguration=true +UseDefaultInterface=true +AddressRandomization=network +ControlPortOverNL80211=true + +[Network] +NameResolvingService=resolvconf +EnableIPv6=true +RoutePriorityOffset=200 + +[Scan] +DisablePeriodicScan=false +DisableRoamingScan=true diff --git a/roles/system/handlers/main.yml b/roles/system/handlers/main.yml new file mode 100644 index 0000000..9a65c3e --- /dev/null +++ b/roles/system/handlers/main.yml @@ -0,0 +1,32 @@ +--- +- name: Notify apparmor kernel parameters + debug: + msg: Please add "apparmor=1 security=apparmor" to your current kernel parameters for apparmor to work + +- name: Notify auditd kernel parameter + debug: + msg: Please add "audit=1" to your current kernel parameters for in-kernel audit support + +- name: Recompile fcron systab + command: + cmd: /usr/bin/fcrontab -z -u systab + removes: /var/spool/fcron/systab.orig + +- name: Regenerate initramfs with mkinitfs + command: '/sbin/mkinitfs -c /etc/mkinitfs/mkinitfs.conf {{ ansible_kernel }}' + args: + removes: /sbin/mkinitfs + +- name: Create snapshots btrfs subvolumes manually + debug: + msg: | + {% if snapshot_tool == 'snapper' %} + Please create .snapshots/ directories and corresponding mounted subvolumes under {{ snapper | map(attribute='subvolume') | join(', ') }} paths manually. + {% elif snapshot_tool == 'btrbk' %} + Please create corresponding subvolumes: + {% for volume in btrbk.volumes %} + {% if volume.snapshot_dir is defined %} + * {{ volume.snapshot_dir }} inside {{ volume.path }} path. + {% endif %} + {% endfor %} + {% endif %} diff --git a/roles/system/tasks/acpi.yml b/roles/system/tasks/acpi.yml new file mode 100644 index 0000000..8f6270b --- /dev/null +++ b/roles/system/tasks/acpi.yml @@ -0,0 +1,19 @@ +--- +# elogind also handles acpi events +- name: acpi | Configure elogind + import_tasks: acpi/elogind.yml + when: seat_manager == 'elogind' + +- name: acpi | Configure apci daemon + when: seat_manager != 'elogind' + block: + - name: acpi | Import tasks for the corresponding acpid daemon + include_tasks: 'acpi/{{ acpid_daemon }}.yml' + when: acpid_daemon == 'acpid' + + - name: acpi | Enable acpid service on runlevel 'default' + service: + name: acpid + runlevel: default + enabled: true + state: started diff --git a/roles/system/tasks/acpi/acpid.yml b/roles/system/tasks/acpi/acpid.yml new file mode 100644 index 0000000..d397b81 --- /dev/null +++ b/roles/system/tasks/acpi/acpid.yml @@ -0,0 +1,23 @@ +--- +- name: acpid | Install acpid and acpi-utils + community.general.apk: + name: acpid, acpi-utils + state: present + +- name: acpid | Copy acpid event handler script + copy: + src: acpi/handler.sh + dest: /etc/acpi/handler.sh + mode: '755' + owner: root + group: root + +- name: acpid | Use process supervisor for acpid service + lineinfile: + path: /etc/conf.d/acpid + search_string: supervisor= + line: supervisor="supervise-daemon" + state: present + owner: root + group: root + mode: '644' diff --git a/roles/system/tasks/acpi/busybox.yml b/roles/system/tasks/acpi/busybox.yml new file mode 100644 index 0000000..f4d7157 --- /dev/null +++ b/roles/system/tasks/acpi/busybox.yml @@ -0,0 +1,16 @@ +--- +- name: busybox's acpid | Create directory for Lid Close event + file: + path: /etc/acpi/LID + state: directory + mode: '755' + owner: root + group: root + +- name: busybox's acpid | Copy event handler for Lid Close + copy: + src: acpi/acpid_lid_close + dest: /etc/acpi/LID/00000080 + mode: '755' + owner: root + group: root diff --git a/roles/system/tasks/acpi/elogind.yml b/roles/system/tasks/acpi/elogind.yml new file mode 100644 index 0000000..f2feda0 --- /dev/null +++ b/roles/system/tasks/acpi/elogind.yml @@ -0,0 +1,25 @@ +--- +- name: elogind | Do not run acpid service + service: + name: acpid + enabled: false + state: stopped + +- name: elogind | Manage acpid configuration + tags: laptop + block: + - name: elogind | Create custom config directory for logind.conf + file: + path: /etc/elogind/logind.conf.d + state: directory + mode: '755' + owner: root + group: root + + - name: elogind | Copy logind.conf + copy: + src: acpi/logind.conf + dest: /etc/elogind/logind.conf.d/custom.conf + mode: '644' + owner: root + group: root diff --git a/roles/system/tasks/apparmor.yml b/roles/system/tasks/apparmor.yml new file mode 100644 index 0000000..0253345 --- /dev/null +++ b/roles/system/tasks/apparmor.yml @@ -0,0 +1,26 @@ +--- +- name: apparmor | Install apparmor and default profiles + community.general.apk: + name: apparmor, apparmor-profiles + state: present + +- name: apparmor | Enable writing cache and faster DFA transition table compression + lineinfile: + path: /etc/apparmor/parser.conf + state: present + search_string: '{{ item }}' + line: '{{ item }}' + owner: root + group: root + mode: '644' + loop: + - write-cache + - Optimize=compress-fast + +# Don't start it yet, as it requires the kernel parameters +- name: apparmor | Add apparmor service to runlevel 'boot' + service: + name: apparmor + runlevel: boot + enabled: true + notify: Notify apparmor kernel parameters diff --git a/roles/system/tasks/auditd.yml b/roles/system/tasks/auditd.yml new file mode 100644 index 0000000..88ce2ab --- /dev/null +++ b/roles/system/tasks/auditd.yml @@ -0,0 +1,33 @@ +--- +- name: auditd | Copy auditd configuration + copy: + src: auditd/auditd.conf + dest: /etc/audit/auditd.conf + mode: '644' + owner: root + group: root + +- name: auditd | Copy audit rules + template: + src: auditd/audit.rules.j2 + dest: /etc/audit/audit.rules + mode: '644' + owner: root + group: root + +- name: auditd | Copy daily cron job to rotate audit log + copy: + src: auditd/cron + dest: /etc/periodic/daily/rotate-audit-log + mode: '755' + owner: root + group: root + when: auditd_logrotate_daily | bool + +- name: auditd | Start auditd service on runlevel 'boot' + service: + name: auditd + enabled: true + state: started + runlevel: boot + notify: Notify auditd kernel parameter diff --git a/roles/system/tasks/cron.yml b/roles/system/tasks/cron.yml new file mode 100644 index 0000000..91be7e0 --- /dev/null +++ b/roles/system/tasks/cron.yml @@ -0,0 +1,23 @@ +--- +# It is recommended to preserve /var/tmp across reboots +- name: cron | Create empty /var/tmp/ weekly job + copy: + src: cron/empty-tmp + dest: /etc/periodic/weekly/empty-tmp + owner: root + group: root + mode: '755' + +- name: cron | Create trimming cron jobs + import_tasks: cron/trim.yml + +- name: cron | Create scrubbing cron jobs for zfs/btrfs + import_tasks: cron/scrub.yml + +- name: cron | Install logrotate and cpulimit + community.general.apk: + name: logrotate, cpulimit + state: present + +- name: cron | Import tasks specific to {{ crond_provider }} + include_tasks: 'cron/{{ crond_provider }}.yml' diff --git a/roles/system/tasks/cron/busybox.yml b/roles/system/tasks/cron/busybox.yml new file mode 100644 index 0000000..84f1ba7 --- /dev/null +++ b/roles/system/tasks/cron/busybox.yml @@ -0,0 +1,9 @@ +--- +# busybox's crond already inherits PATH so no need to do anything. +# It doesn't have allow/deny feature though +- name: crond | Add crond service to runlevel 'default' + service: + name: crond + runlevel: default + enabled: true + state: started diff --git a/roles/system/tasks/cron/cronie.yml b/roles/system/tasks/cron/cronie.yml new file mode 100644 index 0000000..6529a53 --- /dev/null +++ b/roles/system/tasks/cron/cronie.yml @@ -0,0 +1,33 @@ +--- +- name: cronie | Install cronie package + community.general.apk: + name: cronie + state: present + +- name: cronie | Allow access to crontabs for only root and {{ username }} + copy: + content: | + {{ username }} + dest: /etc/cron.allow + owner: root + group: root + mode: '644' + +# btrbk runs btrfs command directly (without specifying /sbin prefix), +# hence we need to inherit PATH here. Also log to syslog +# (the default PATH is /usr/local/bin:/bin:/usr/bin) +- name: cronie | Configure command options for cronie service + copy: + content: | + CRON_OPTS="-s -P" + dest: /etc/conf.d/cronie + owner: root + group: root + mode: '644' + +- name: cronie | Start cronie service in runlevel default + service: + name: cronie + runlevel: default + state: started + enabled: true diff --git a/roles/system/tasks/cron/fcron.yml b/roles/system/tasks/cron/fcron.yml new file mode 100644 index 0000000..d9d26d2 --- /dev/null +++ b/roles/system/tasks/cron/fcron.yml @@ -0,0 +1,43 @@ +--- +- name: fcron | Install fcron package + community.general.apk: + name: fcron + state: present + +- name: fcron | Deny all users to access crontabs + copy: + content: | + all + dest: /etc/fcron/fcron.deny + owner: root + group: fcron + mode: '640' + +- name: fcron | Allow access to crontabs for root and {{ username }} + copy: + content: | + root + {{ username }} + dest: /etc/fcron/fcron.allow + owner: root + group: fcron + mode: '640' + +# The default PATH is /bin:/usr/bin +- name: fcron | Set PATH inside system crontab (systab) + lineinfile: + path: /var/spool/fcron/systab.orig + line: "PATH=/bin:/usr/bin:/sbin:/usr/sbin" + insertbefore: BOF + mode: '640' + owner: root + group: root + state: present + notify: Recompile fcron systab + +- name: fcron | Start fcron service on runlevel default + service: + name: fcron + runlevel: default + state: started + enabled: true diff --git a/roles/system/tasks/cron/scrub.yml b/roles/system/tasks/cron/scrub.yml new file mode 100644 index 0000000..a3a87dd --- /dev/null +++ b/roles/system/tasks/cron/scrub.yml @@ -0,0 +1,23 @@ +--- +- name: cron | Create BTRFS scrub monthly job + copy: + content: | + #!/bin/sh + exec /sbin/btrfs scrub start -B {{ item }} + dest: /etc/periodic/monthly/scrub-btrfs-{{ item | regex_replace('/', '@') }} + owner: root + group: root + mode: '755' + # PERF: a shorter version but requires `py3-jmespath`: '{{ ansible_mounts | json_query("[?fstype == `btrfs`].mount") }}' + loop: '{{ ansible_mounts | selectattr("fstype", "equalto", "btrfs") | map(attribute="mount") | list }}' + +- name: cron | Create ZFS pool scrub monthly job + copy: + content: | + #!/bin/sh + exec /usr/sbin/zpool scrub -w {{ item }} + dest: /etc/periodic/monthly/scrub-zfs-{{ item }} + mode: '755' + owner: root + group: root + loop: '{{ ansible_zfs_pools | map(attribute="name") | list }}' diff --git a/roles/system/tasks/cron/trim.yml b/roles/system/tasks/cron/trim.yml new file mode 100644 index 0000000..0eec9ec --- /dev/null +++ b/roles/system/tasks/cron/trim.yml @@ -0,0 +1,25 @@ +--- +# NOTE: dmcrypt discourages using `discard` mount option (as in BTRFS on LUKS), +# so running trim manually in a cron job +- name: cron | Create fstrim weekly job + copy: + src: cron/fstrim + dest: /etc/periodic/weekly/fstrim + owner: root + group: root + mode: '755' + +- name: cron | Create ZFS pool trim weekly job + copy: + content: | + #!/bin/sh + if /usr/sbin/zpool status {{ item }} | grep -qF "trimming"; then + exec /usr/sbin/zpool wait -t trim {{ item }} + else + exec /usr/sbin/zpool trim -w {{ item }} + fi + dest: /etc/periodic/weekly/trim-zfs-{{ item }} + mode: '755' + owner: root + group: root + loop: '{{ ansible_zfs_pools | map(attribute="name") | list }}' diff --git a/roles/system/tasks/devd.yml b/roles/system/tasks/devd.yml new file mode 100644 index 0000000..d2ea669 --- /dev/null +++ b/roles/system/tasks/devd.yml @@ -0,0 +1,50 @@ +--- +# https://arvanta.net/alpine/libudev-zero/ +- name: devd | Specific tasks for non-udev device manager + when: device_manager != 'udev' + block: + - name: devd | Install libudev-zero + community.general.apk: + name: libudev-zero + state: present + + - name: devd | Enable hwdrivers service on runlevel sysinit + service: + name: hwdrivers + runlevel: sysinit + enabled: true + +- name: devd | udev doesn't need hwdrivers service + service: + name: hwdrivers + runlevel: sysinit + enabled: false + when: device_manager == 'udev' + +# https://github.com/illiliti/libudev-zero/blob/master/contrib/mdev.conf +# mdevd-openrc on Alpine already comes with -O4 flag by default +- name: devd | Specific tasks for busybox's mdev + when: device_manager == 'mdev' + block: + - name: mdev | Install libudev-zero-helper + community.general.apk: + name: libudev-zero-helper + state: present + + - name: mdev | Enable hotplugging for DRM and INPUT uevents + lineinfile: + path: /etc/mdev.conf + line: '{{ item }}' + state: present + mode: '644' + owner: root + group: root + loop: + - 'SUBSYSTEM=drm;.* root:video 660 */usr/libexec/libudev-zero-helper' + - 'SUBSYSTEM=input;.* root:input 660 */usr/libexec/libudev-zero-helper' + +- name: devd | Run setup-devd script + command: + cmd: '/sbin/setup-devd -C {{ device_manager }}' + creates: '/etc/runlevels/sysinit/{{ device_manager }}' + failed_when: false diff --git a/roles/system/tasks/dns.yml b/roles/system/tasks/dns.yml new file mode 100644 index 0000000..94c1553 --- /dev/null +++ b/roles/system/tasks/dns.yml @@ -0,0 +1,17 @@ +--- +- name: dns | Install openresolv and {{ dns_resolver }} + community.general.apk: + name: openresolv, {{ dns_resolver }} + state: present + +# Static import_tasks doesn't work here +- name: dns | Configure {{ dns_resolver }} + include_tasks: 'dns/{{ dns_resolver }}.yml' + +- name: dns | Update resolvconf config + template: + src: dns/resolvconf.j2 + dest: /etc/resolvconf.conf + owner: root + group: root + mode: '644' diff --git a/roles/system/tasks/dns/dnscrypt-proxy.yml b/roles/system/tasks/dns/dnscrypt-proxy.yml new file mode 100644 index 0000000..0888eff --- /dev/null +++ b/roles/system/tasks/dns/dnscrypt-proxy.yml @@ -0,0 +1,112 @@ +--- +# Firefox needs to be setup manually: +# https://github.com/DNSCrypt/dnscrypt-proxy/wiki/Local-DoH +- name: dnscrypt-proxy | Prerequisite setup for local DoH server + when: dnscrypt.local_doh.enabled + block: + - name: dnscrypt-proxy | Generate private key for local DoH server + community.crypto.openssl_privatekey: + path: '/etc/dnscrypt-proxy/{{ ansible_hostname }}.pem' + type: Ed25519 + mode: '640' + owner: root + group: dnscrypt + state: present + + - name: dnscrypt-proxy | Generate self-signed certificate for local DoH server + community.crypto.x509_certificate: + path: '/etc/dnscrypt-proxy/{{ ansible_hostname }}.crt' + privatekey_path: '/etc/dnscrypt-proxy/{{ ansible_hostname }}.pem' + provider: selfsigned + selfsigned_not_after: +5000d + selfsigned_digest: sha512 + mode: '640' + owner: root + group: dnscrypt + state: present + +- name: dnscrypt-proxy | Copy blocked-ips.txt for rebinding protection + copy: + src: dns/blocked-ips.txt + dest: /etc/dnscrypt-proxy/blocked-ips.txt + mode: '644' + owner: root + group: root + +- name: dnscrypt-proxy | Copy cloaking-rules.txt + copy: + src: dns/cloaking-rules.txt + dest: /etc/dnscrypt-proxy/cloaking-rules.txt + mode: '644' + owner: root + group: root + +- name: dnscrypt-proxy | Configure DNS-based adblocking + when: dnscrypt.adblock + block: + - name: dnscrypt-proxy | Create adblock directory for blocklist configurations + file: + path: /etc/dnscrypt-proxy/adblock + owner: root + group: root + mode: '755' + state: directory + + - name: dnscrypt-proxy | Copy blocklists + copy: + src: 'dns/{{ item }}' + dest: '/etc/dnscrypt-proxy/adblock/{{ item }}' + owner: root + group: root + mode: '644' + loop: + - domains-blocklist.conf + - domains-blocklist-local-additions.txt + + - name: dnscrypt-proxy | Download generate-domains-blocklist.py + get_url: + url: https://raw.githubusercontent.com/DNSCrypt/dnscrypt-proxy/master/utils/generate-domains-blocklist/generate-domains-blocklist.py + dest: /etc/dnscrypt-proxy/adblock/generate-domains-blocklist.py + checksum: sha256:5dfb8cab46384df6194724d35fbe0e5fc87945c63f0283f06534f66f581e0e1c + mode: '644' + owner: root + group: root + + - name: dnscrypt-proxy | Generate domain blocklist the 1st time + command: + cmd: /usr/bin/python3 generate-domains-blocklist.py -i -a /dev/null -r /dev/null -c domains-blocklist.conf -o /etc/dnscrypt-proxy/blocked-names.txt + chdir: /etc/dnscrypt-proxy/adblock + creates: /etc/dnscrypt-proxy/blocked-names.txt + failed_when: false + + - name: dnscrypt-proxy | Ensure proper permission on blocked-names.txt file + file: + path: /etc/dnscrypt-proxy/blocked-names.txt + mode: '644' + owner: root + group: root + state: file + + - name: dnscrypt-proxy | Add daily cron job to update the blocklist + copy: + src: dns/update_dnscrypt_blocklist + dest: /etc/periodic/daily/update_dnscrypt_blocklist + mode: '755' + owner: root + group: root + +- name: dnscrypt-proxy | Copy dnscrypt-proxy config + template: + src: dns/dnscrypt-proxy.j2 + dest: /etc/dnscrypt-proxy/dnscrypt-proxy.toml + mode: '644' + owner: root + group: root + validate: /usr/bin/dnscrypt-proxy -check -config %s + +- name: dnscrypt-proxy | Start dnscrypt-proxy service on runlevel 'default' + service: + name: dnscrypt-proxy + enabled: true + state: started + runlevel: default diff --git a/roles/system/tasks/dns/unbound.yml b/roles/system/tasks/dns/unbound.yml new file mode 100644 index 0000000..8706e4c --- /dev/null +++ b/roles/system/tasks/dns/unbound.yml @@ -0,0 +1,111 @@ +--- +- name: unbound | Install dns-root-hints + community.general.apk: + name: dns-root-hints + state: present + +- name: unbound | Create /dev directory inside unbound chroot + file: + path: /etc/unbound/dev + state: directory + owner: root + group: root + mode: '755' + +# We are operating on special devices +# copy module doesn't seem to work +- name: unbound | Copy needed devices to unbound chroot + command: + cmd: /bin/cp -a /dev/urandom /dev/null /etc/unbound/dev/ + creates: /etc/unbound/dev/urandom + +# unbound user needs write permission to the anchor file, and also for the +# directory it is in (to create a temporary file) +- name: unbound | Create var directory inside unbound chroot + file: + path: /etc/unbound/var + state: directory + owner: root + group: unbound + mode: '775' + +# unbound-anchor gives errors about connecting to IPv6 servers +- name: unbound | Generate trusted anchor file + command: + cmd: /usr/sbin/unbound-anchor -a /etc/unbound/var/trusted-key.key + creates: /etc/unbound/var/trusted-key.key + failed_when: false + +- name: unbound | Ensure proper permissions on trusted anchor file + file: + path: /etc/unbound/var/trusted-key.key + state: file + owner: unbound + group: unbound + mode: '640' + +- name: unbound | Gather package facts + package_facts: + manager: 'apk' + +# We only need to run the DNS root hints update the 1st time, as +# dns-root-hints package comes with a monthly crontab +- name: unbound | Check the current DNS root zone version + lineinfile: + backup: true + path: /usr/share/dns-root-hints/named.root + regexp: '^;[ \t]+related version of root zone:[ \t]+{{ ansible_facts.packages["dns-root-hints"][0].version }}$' + state: absent + check_mode: true + register: root_hints_check + changed_when: false + +- name: unbound | Get the latest DNS root hints + command: + cmd: /usr/bin/update-dns-root-hints + removes: /usr/bin/update-dns-root-hints + when: root_hints_check.found == 1 + +- name: unbound | Copy the updated root hints file to unbound chroot + copy: + src: /usr/share/dns-root-hints/named.root + dest: /etc/unbound/var/named.root + owner: root + group: unbound + mode: '640' + +- name: unbound | Let dns-root-hints cron job copy root hints to unbound chroot + blockinfile: + path: /etc/periodic/monthly/dns-root-hints + block: | + updated_file="/usr/share/dns-root-hints/named.root" + current_file="/etc/unbound/var/named.root" + drh_new_ver=$(/bin/grep "related version of root zone:" $updated_file | /bin/grep -E -o '[0-9]{10}') + drh_current_ver=$(/bin/grep "related version of root zone:" $current_file | /bin/grep -E -o '[0-9]{10}') + + if [ "$drh_new_ver" != "$drh_current_ver" ]; then + /bin/cp -f $updated_file $current_file + /bin/chown root:unbound $current_file + /bin/chmod 0640 $current_file + fi + marker: '# {mark} COPY THE UPDATED ROOT HINTS TO UNBOUND CHROOT' + insertafter: 'EOF' + owner: root + group: root + mode: '755' + +- name: unbound | Copy unbound config + template: + src: dns/unbound.j2 + dest: /etc/unbound/unbound.conf + owner: root + group: root + mode: '644' + validate: /usr/sbin/unbound-checkconf %s + +- name: unbound | Add unbound service to runlevel 'default' + service: + name: unbound + runlevel: default + enabled: true + state: started diff --git a/roles/system/tasks/earlyoom.yml b/roles/system/tasks/earlyoom.yml new file mode 100644 index 0000000..639539a --- /dev/null +++ b/roles/system/tasks/earlyoom.yml @@ -0,0 +1,20 @@ +--- +- name: earlyoom | Install earlyoom package + community.general.apk: + name: earlyoom + state: present + +- name: earlyoom | Copy service configuration + template: + src: earlyoom/conf.j2 + dest: /etc/conf.d/earlyoom + mode: '644' + owner: root + group: root + +- name: earlyoom | Start earlyoom service on runlevel 'default' + service: + name: earlyoom + runlevel: default + enabled: true + state: started diff --git a/roles/system/tasks/essential.yml b/roles/system/tasks/essential.yml new file mode 100644 index 0000000..e14c083 --- /dev/null +++ b/roles/system/tasks/essential.yml @@ -0,0 +1,105 @@ +--- +- name: essential | Change repository URLs + template: + src: essential/repositories.j2 + dest: /etc/apk/repositories + owner: root + group: root + mode: '644' + +- name: essential | Update repository cache and the system + community.general.apk: + available: true + upgrade: true + update_cache: true + +- name: essential | Install common dependencies + community.general.apk: + name: zstd, dbus, font-terminus, shadow-login + state: present + +- name: essential | Enable logging and unicode support for openrc + lineinfile: + path: /etc/rc.conf + state: present + search_string: '{{ item }}=' + line: '{{ item }}="YES"' + owner: root + group: root + mode: '644' + loop: + - rc_logger + - unicode + +# https://wiki.gentoo.org/wiki/Elogind +# elogind still requires 'cgroup-hybrid' useflag +- name: essential | Explicitly enable only cgroup v2 for OpenRC + lineinfile: + path: /etc/rc.conf + state: present + search_string: rc_cgroup_mode= + line: rc_cgroup_mode="unified" + owner: root + group: root + mode: '644' + when: seat_manager != 'elogind' + +- name: essential | Change the default motd + template: + src: essential/motd.j2 + dest: /etc/motd + owner: root + group: root + mode: '644' + +- name: essential | Use zstd for initramfs + lineinfile: + path: /etc/mkinitfs/mkinitfs.conf + state: present + search_string: initfscomp= + line: initfscomp="zstd" + owner: root + group: root + mode: '644' + notify: Regenerate initramfs + +- name: essential | Blacklist bluetooth related kernel modules + community.general.kernel_blacklist: + name: '{{ item }}' + state: present + loop: + - vivid + - bluetooth + - btusb + +- name: essential | Use /var/tmp for coredumps + ansible.posix.sysctl: + name: kernel.core_pattern + value: /var/tmp/core-%e.%p.%h.%t + state: present + +- name: essential | Change the tty font to {{ console_font }} + lineinfile: + path: /etc/conf.d/consolefont + state: present + regexp: '^consolefont=' + line: 'consolefont="{{ console_font }}"' + owner: root + group: root + mode: '644' + +- name: essential | Start services on runlevel 'boot' + service: + name: '{{ item }}' + runlevel: boot + enabled: true + state: started + loop: ['consolefont', 'seedrng', 'syslog'] + +- name: essential | Start services on runlevel 'default' + service: + name: '{{ item }}' + runlevel: default + enabled: true + state: started + loop: ['dbus', 'cgroups'] diff --git a/roles/system/tasks/fstab.yml b/roles/system/tasks/fstab.yml new file mode 100644 index 0000000..66f225e --- /dev/null +++ b/roles/system/tasks/fstab.yml @@ -0,0 +1,58 @@ +--- +# pyenv, doom (cli of Doom Emacs), ... need exec inside /tmp +- name: fstab | Harden mount options for /tmp + ansible.posix.mount: + src: tmpfs + path: /tmp + fstype: tmpfs + opts: rw,nosuid,nodev,size=4G,mode=1777 + state: present + +# /run is mounted with exec by default +- name: fstab | Harden mount options for /run + ansible.posix.mount: + src: tmpfs + path: /run + fstype: tmpfs + opts: rw,nosuid,nodev,noexec,size=1G,mode=0755 + state: present + +# polkit daemon obviously needs access to /proc to work +# Note: Add the normal user to polkitd group afterward +- name: fstab | Configure /proc restriction + vars: + proc_group: '{{ (use_polkit or (seat_manager == "elogind")) | ternary("polkitd", "wheel") }}' + block: + # Busybox's mount doesn't interpret group name in GID, so check it + # wheel group on Alpine by default has GID=10 + - name: fstab | Check GID of group {{ proc_group }} # noqa: risky-shell-pipe + shell: /usr/bin/getent group {{ proc_group }} | awk -F':' '{print $3}' + register: proc_gid + changed_when: false + + - name: fstab | Restrict read access on /proc for group {{ proc_group }} + ansible.posix.mount: + src: proc + path: /proc + fstype: proc + opts: 'rw,nosuid,nodev,noexec,hidepid=2,gid={{ proc_gid.stdout }}' + state: present + +- name: fstab | Disable UEFI variable access + ansible.posix.mount: + src: efivarfs + path: /sys/firmware/efi/efivars + fstype: efivars + opts: ro,nosuid,nodev,noexec + state: present + when: disable_uefi_access + +- name: fstab | Allow UEFI variable access + lineinfile: + path: /etc/fstab + search_string: /sys/firmware/efi/efivars + state: absent + owner: root + group: root + mode: '644' + when: not disable_uefi_access diff --git a/roles/system/tasks/libvirt.yml b/roles/system/tasks/libvirt.yml new file mode 100644 index 0000000..4d1ce6c --- /dev/null +++ b/roles/system/tasks/libvirt.yml @@ -0,0 +1,34 @@ +--- +- name: libvirt | Install libvirt and qemu + community.general.apk: + name: libvirt-daemon, qemu-img, qemu-system-x86_64, qemu-system-arm, qemu-system-aarch64, qemu-modules + state: present + +# This is for PulseAudio +- name: libvirt | Allow the normal user to interact with qemu system instance + lineinfile: + path: /etc/libvirt/qemu.conf + owner: root + group: root + mode: '644' + regexp: '^#?user = "' + line: 'user = "{{ username }}"' + +- name: libvirt | Use file-based permissions when polkit is not available + lineinfile: + path: /etc/libvirt/{{ item[0] }}.conf + owner: root + group: root + mode: '644' + regexp: '^#?{{ item[1] | regex_replace(" =.*$", "") }}' + line: '{{ item[1] }}' + loop: '{{ libvirt_daemons | product(libvirt_daemons_conf) | list }}' + when: (not use_polkit) and (seat_manager != 'elogind') + +- name: libvirt | Start modular services on runlevel 'default' + service: + name: '{{ item }}' + runlevel: default + enabled: true + state: started + loop: '{{ libvirt_daemons + ["virtlogd", "virtlockd"] }}' diff --git a/roles/system/tasks/main.jsonnet b/roles/system/tasks/main.jsonnet new file mode 100644 index 0000000..872268f --- /dev/null +++ b/roles/system/tasks/main.jsonnet @@ -0,0 +1,39 @@ +local task(item) = + local type = std.type(item); + if type == 'string' then + { + name: 'Configure ' + item, + import_tasks: item + '.yml', + tags: item, + } + else if type == 'object' then + { + name: 'Configure ' + item.name, + import_tasks: item.name + '.yml', + tags: [item.name], + } + item.opts + else {}; + +local modules = [ + 'acpi', + 'apparmor', + 'auditd', + 'cron', + 'devd', + 'dns', + 'earlyoom', + 'essential', + 'fstab', + 'libvirt', + 'networking', + 'nftables', + 'ntpd', + 'seat', + 'snapshot', + { name: 'tlp', opts: { tags+: ['laptop'] } }, + 'usbguard', + 'user', + 'zram', +]; + +std.manifestYamlDoc([task(x) for x in modules], quote_keys=false) diff --git a/roles/system/tasks/main.yml b/roles/system/tasks/main.yml new file mode 100644 index 0000000..03b51bf --- /dev/null +++ b/roles/system/tasks/main.yml @@ -0,0 +1,64 @@ +--- + +# Auto-generated with Jsonnet from main.jsonnet +# Do NOT edit!! + +- import_tasks: "acpi.yml" + name: "Configure acpi" + tags: "acpi" +- import_tasks: "apparmor.yml" + name: "Configure apparmor" + tags: "apparmor" +- import_tasks: "auditd.yml" + name: "Configure auditd" + tags: "auditd" +- import_tasks: "cron.yml" + name: "Configure cron" + tags: "cron" +- import_tasks: "devd.yml" + name: "Configure devd" + tags: "devd" +- import_tasks: "dns.yml" + name: "Configure dns" + tags: "dns" +- import_tasks: "earlyoom.yml" + name: "Configure earlyoom" + tags: "earlyoom" +- import_tasks: "essential.yml" + name: "Configure essential" + tags: "essential" +- import_tasks: "fstab.yml" + name: "Configure fstab" + tags: "fstab" +- import_tasks: "libvirt.yml" + name: "Configure libvirt" + tags: "libvirt" +- import_tasks: "networking.yml" + name: "Configure networking" + tags: "networking" +- import_tasks: "nftables.yml" + name: "Configure nftables" + tags: "nftables" +- import_tasks: "ntpd.yml" + name: "Configure ntpd" + tags: "ntpd" +- import_tasks: "seat.yml" + name: "Configure seat" + tags: "seat" +- import_tasks: "snapshot.yml" + name: "Configure snapshot" + tags: "snapshot" +- import_tasks: "tlp.yml" + name: "Configure tlp" + tags: + - "tlp" + - "laptop" +- import_tasks: "usbguard.yml" + name: "Configure usbguard" + tags: "usbguard" +- import_tasks: "user.yml" + name: "Configure user" + tags: "user" +- import_tasks: "zram.yml" + name: "Configure zram" + tags: "zram" diff --git a/roles/system/tasks/networking.yml b/roles/system/tasks/networking.yml new file mode 100644 index 0000000..d2f069a --- /dev/null +++ b/roles/system/tasks/networking.yml @@ -0,0 +1,47 @@ +--- +- name: networking | Overwrite /etc/hosts to support IPv6 + copy: + src: networking/hosts + dest: /etc/hosts + owner: root + group: root + mode: '644' + +# NOTE: already set in /lib/sysctl.d/00-alpine.conf but it doesn't hurt re-apply +- name: networking | Set IPv6 Privacy Extension (RFC 4941) + ansible.posix.sysctl: + name: '{{ (item | split("=") | map("trim") | list)[0] }}' + value: '{{ (item | split("=") | map("trim") | list)[1] }}' + state: present + loop: + - net.ipv6.conf.all.use_tempaddr = 2 + - net.ipv6.conf.default.use_tempaddr = 2 + +- name: networking | Set IPv6 stable privacy address (RFC 7217) + ansible.posix.sysctl: + name: '{{ (item | split("=") | map("trim") | list)[0] }}' + value: '{{ (item | split("=") | map("trim") | list)[1] }}' + state: present + loop: + - net.ipv6.conf.default.addr_gen_mode = 2 + - net.ipv6.conf.all.addr_gen_mode = 2 + - net.ipv6.conf.default.stable_secret = {{ (2**16 - 1) | random_hex }}:{{ (2**16 - 1) | random_hex }}:{{ (2**16 - 1) | random_hex }}:{{ (2**16 - 1) | random_hex }}:{{ (2**16 - 1) | random_hex }}:{{ (2**16 - 1) | random_hex }}:{{ (2**16 - 1) | random_hex }}:{{ (2**16 - 1) | random_hex }} # noqa: yaml[line-length] + when: ipv6_stable_privacy_addr | bool + +- name: networking | Install {{ dhcp_client }} + community.general.apk: + name: '{{ dhcp_client }}' + state: present + when: dhcp_client != 'udhcpc' + +# Assume 'networking' service is already started on 'boot' before the playbook is run +- name: networking | Tweak the default 'networking' service + import_tasks: networking/networking.yml + +- name: networking | Configure iwd + import_tasks: networking/iwd.yml + tags: laptop + +- name: networking | Configure connman + import_tasks: networking/connman.yml + when: dhcp_client == 'connman' diff --git a/roles/system/tasks/networking/connman.yml b/roles/system/tasks/networking/connman.yml new file mode 100644 index 0000000..2ffb371 --- /dev/null +++ b/roles/system/tasks/networking/connman.yml @@ -0,0 +1,47 @@ +--- +# connman itself is already installed in the parent task +- name: networking | Install connman packages + community.general.apk: + name: connman-nftables, connman-resolvconf + state: present + +- name: networking | Copy connman configuration + copy: + src: networking/connman/main.conf + dest: /etc/connman/main.conf + owner: root + group: root + mode: '644' + +- name: networking | Make sure /var/lib/connman directory exists + file: + path: /var/lib/connman + mode: '755' + owner: root + group: root + state: directory + +- name: networking | Configure connman Ethernet interfaces + template: + src: networking/connman-service.config.j2 + dest: /var/lib/connman/ethernet_services.config + owner: root + group: root + mode: '644' + +- name: networking | Copy connmand service configuration + copy: + src: networking/connman/connman.confd + dest: /etc/conf.d/connman + owner: root + group: root + mode: '644' + +- name: networking | Start connman services on runlevel 'default' + service: + name: '{{ item }}' + runlevel: default + enabled: true + loop: + - connman + - connman-resolvconf diff --git a/roles/system/tasks/networking/iwd.yml b/roles/system/tasks/networking/iwd.yml new file mode 100644 index 0000000..c882b78 --- /dev/null +++ b/roles/system/tasks/networking/iwd.yml @@ -0,0 +1,31 @@ +--- +- name: networking | Install iwd package + community.general.apk: + name: iwd + state: present + when: not iwd_without_dbus + +# NOTE: connmanctl can't scan wifi with eiwd so we need iw +# - iw dev scan | grep SSID +# - printf | iwd_passphrase > /var/lib/iwd/.psk +- name: networking | Install eiwd and iw packages + community.general.apk: + name: eiwd, iw + state: present + when: iwd_without_dbus + +- name: networking | Copy iwd configuration + copy: + src: networking/iwd/main.conf + dest: /etc/iwd/main.conf + owner: root + group: root + mode: '644' + +- name: networking | Start iwd service on runlevel 'boot' + vars: + iwd_service: '{{ iwd_without_dbus | ternary("eiwd", "iwd") }}' + service: + name: '{{ iwd_service }}' + runlevel: default + enabled: true diff --git a/roles/system/tasks/networking/networking.yml b/roles/system/tasks/networking/networking.yml new file mode 100644 index 0000000..0d6ecbd --- /dev/null +++ b/roles/system/tasks/networking/networking.yml @@ -0,0 +1,25 @@ +--- +- name: Reconfigure 'networking' service + when: dhcp_client in ['udhcpc', 'dhcpcd', 'dhclient'] + block: + - name: networking | Configure /etc/network/interfaces + template: + src: networking/interfaces.j2 + dest: /etc/network/interfaces + owner: root + group: root + mode: '644' + + - name: networking | Restart 'networking' service + service: + name: networking + state: restarted + enabled: true + runlevel: boot + +- name: networking | Stop 'networking' service on boot in favor of {{ dhcp_client }} + service: + name: networking + enabled: false + runlevel: boot + when: not dhcp_client in ['udhcpc', 'dhcpcd', 'dhclient'] diff --git a/roles/system/tasks/nftables.yml b/roles/system/tasks/nftables.yml new file mode 100644 index 0000000..41721ed --- /dev/null +++ b/roles/system/tasks/nftables.yml @@ -0,0 +1,19 @@ +--- +- name: nftables | Install nftables + community.general.apk: + name: nftables + state: present + +- name: nftables | Copy firewall configuration + template: + src: nftables/nftables.j2 + dest: /etc/nftables.nft + owner: root + group: root + mode: '644' + +- name: nftables | Add nftables service to runlevel 'boot' + service: + name: nftables + runlevel: boot + enabled: true diff --git a/roles/system/tasks/ntpd.yml b/roles/system/tasks/ntpd.yml new file mode 100644 index 0000000..7dc040f --- /dev/null +++ b/roles/system/tasks/ntpd.yml @@ -0,0 +1,10 @@ +--- +- name: ntpd | Install package {{ ntp_client }} + community.general.apk: + name: '{{ ntp_client }}' + state: present + when: ntp_client != 'busybox' + +# NOTE: busybox's ntpd should be stopped (and disabled) manually before this +- name: ntpd | Import corresponding tasks + include_tasks: 'ntpd/{{ ntp_client }}.yml' diff --git a/roles/system/tasks/ntpd/README.md b/roles/system/tasks/ntpd/README.md new file mode 100644 index 0000000..d9263bf --- /dev/null +++ b/roles/system/tasks/ntpd/README.md @@ -0,0 +1,27 @@ +# Notes + +Tasks not needed in NTP client mode but might come handy in the future. + +## Generate NTP symetric key for chronyd + +**NOTE**: NTS should be used if the server is publicly accessible. Otherwise, for a private NTP server, symetric key is sufficient. + +```yaml +- name: chrony | Generate the symetric key with chronyc + command: + cmd: /usr/bin/chronyc keygen {{ (2**32 - 1) | random }} SHA512 256 + creates: /etc/chrony/chrony.keys + register: chrony_keys + +- name: chrony | Create chrony.keys file from chronyc output + copy: + content: | + {{ chrony_keys['stdout'] }} + dest: /etc/chrony/chrony.keys + mode: '400' + owner: chrony + group: root + when: chrony_keys is defined +``` + +Then add `keyfile /etc/chrony/chrony.keys` to ***/etc/chrony/chrony.conf*** file. diff --git a/roles/system/tasks/ntpd/busybox.yml b/roles/system/tasks/ntpd/busybox.yml new file mode 100644 index 0000000..10dc8ea --- /dev/null +++ b/roles/system/tasks/ntpd/busybox.yml @@ -0,0 +1,18 @@ +--- +- name: ntpd | Adjust ntpd service configuration + copy: # noqa: jinja[spacing] + content: > + NTPD_OPTS="-N + {%- for pool in ntp_opts.pools %} -p {{ pool }}{% endfor %} + {%- for server in ntp_opts.servers %} -p {{ server }}{% endfor %}" + dest: /etc/conf.d/ntpd + owner: root + group: root + mode: '644' + +- name: ntpd | Start ntpd service at runlevel 'default' + service: + name: ntpd + runlevel: default + enabled: true + state: started diff --git a/roles/system/tasks/ntpd/chrony.yml b/roles/system/tasks/ntpd/chrony.yml new file mode 100644 index 0000000..2c04ceb --- /dev/null +++ b/roles/system/tasks/ntpd/chrony.yml @@ -0,0 +1,32 @@ +--- +# NOTE: chrony package comes with a default /etc/chrony/chrony.conf +# => No need to create a parent directory here +- name: chrony | Copy chrony.conf + template: + src: ntpd/chrony.conf.j2 + dest: /etc/chrony/chrony.conf + owner: root + group: root + mode: '644' + +# - Basically just run chronyd on foreground +# - https://wiki.archlinux.org/title/Chrony#For_intermittently_running_desktops +- name: chrony | Change default configuration for chronyd service + lineinfile: + path: /etc/conf.d/chronyd + line: '{{ item }}' + search_string: '{{ item | regex_replace("=.*$", "") }}' + owner: root + group: root + mode: '644' + state: present + loop: + - FAST_STARTUP=yes + - ARGS="-s -r" + +- name: chrony | Start chronyd service on runlevel 'default' + service: + name: chronyd + enabled: true + state: started + runlevel: default diff --git a/roles/system/tasks/ntpd/ntpsec.yml b/roles/system/tasks/ntpd/ntpsec.yml new file mode 100644 index 0000000..cd0173a --- /dev/null +++ b/roles/system/tasks/ntpd/ntpsec.yml @@ -0,0 +1,15 @@ +--- +- name: ntpsec | Copy ntp.conf + template: + src: ntpd/ntp.conf.j2 + dest: /etc/ntp.conf + owner: root + group: root + mode: '644' + +- name: ntpsec | Start ntpsec service on runlevel 'default' + service: + name: ntpsec + enabled: true + state: started + runlevel: default diff --git a/roles/system/tasks/ntpd/openntpd.yml b/roles/system/tasks/ntpd/openntpd.yml new file mode 100644 index 0000000..0b4acb8 --- /dev/null +++ b/roles/system/tasks/ntpd/openntpd.yml @@ -0,0 +1,17 @@ +--- +# Validation can take up to 15s, so be patient +- name: openntpd | Copy ntpd.conf + template: + src: ntpd/ntpd.conf.j2 + dest: /etc/ntpd.conf + owner: root + group: root + mode: '644' + validate: /usr/sbin/ntpd -n -f %s + +- name: openntpd | Start openntpd service on runlevel 'default' + service: + name: openntpd + enabled: true + state: started + runlevel: default diff --git a/roles/system/tasks/seat.yml b/roles/system/tasks/seat.yml new file mode 100644 index 0000000..ee618f0 --- /dev/null +++ b/roles/system/tasks/seat.yml @@ -0,0 +1,46 @@ +--- +- name: seat | Start elogind + when: seat_manager == 'elogind' + block: + - name: seat | Install elogind and polkit-elogind + community.general.apk: + name: elogind, polkit-elogind + state: present + + # Some acpi functions might not work if elogind is started on + # runlevel 'default' + - name: seat | Start elogind service on runlevel 'default' + service: + name: elogind + runlevel: default + enabled: true + state: started + +- name: seat | Start seatd + when: seat_manager == 'seatd' + block: + - name: seat | Install seatd + community.general.apk: + name: seatd + state: present + + - name: seat | Start seatd service on runlevel 'default' + service: + name: seatd + runlevel: default + enabled: true + state: started + +- name: seat | Install stuff when elogind doesn't exist + when: seat_manager != 'elogind' + block: + - name: seat | Install pam-rundir + community.general.apk: + name: pam-rundir + state: present + + - name: seat | Install polkit + community.general.apk: + name: polkit + state: present + when: use_polkit diff --git a/roles/system/tasks/snapshot.yml b/roles/system/tasks/snapshot.yml new file mode 100644 index 0000000..a4c99cd --- /dev/null +++ b/roles/system/tasks/snapshot.yml @@ -0,0 +1,3 @@ +--- +- name: snapshot | Setup {{ snapshot_tool }} + include_tasks: 'snapshot/{{ snapshot_tool }}.yml' diff --git a/roles/system/tasks/snapshot/btrbk.yml b/roles/system/tasks/snapshot/btrbk.yml new file mode 100644 index 0000000..952cf42 --- /dev/null +++ b/roles/system/tasks/snapshot/btrbk.yml @@ -0,0 +1,26 @@ +--- +- name: btrbk | Install btrbk package + community.general.apk: + name: btrbk + state: present + +- name: btrbk | Copy config btrbk.conf + template: + src: snapshot/btrbk.conf.j2 + dest: /etc/btrbk/btrbk.conf + mode: '644' + owner: root + group: root + notify: + - Create snapshots btrfs subvolumes manually + +- name: btrbk | Create btrbk crontabs + copy: + content: | + #!/bin/sh + TZ=UTC exec /usr/bin/btrbk -q {{ item.value }} + dest: /etc/periodic/{{ item.key }}/btrbk + mode: '755' + owner: root + group: root + loop: '{{ btrbk.cron | dict2items }}' diff --git a/roles/system/tasks/snapshot/sanoid.yml b/roles/system/tasks/snapshot/sanoid.yml new file mode 100644 index 0000000..cdbc3b9 --- /dev/null +++ b/roles/system/tasks/snapshot/sanoid.yml @@ -0,0 +1,14 @@ +--- +- name: sanoid | Install sanoid and syncoid packages + community.general.apk: + name: sanoid, syncoid + state: present + +# 'sanoid' APK package comes with a 15min cronjob, so we only need to modify the configuration file +- name: sanoid | Create configuration file + template: + src: snapshot/sanoid.conf.j2 + dest: /etc/sanoid/sanoid.conf + mode: '644' + owner: root + group: root diff --git a/roles/system/tasks/snapshot/snapper.yml b/roles/system/tasks/snapshot/snapper.yml new file mode 100644 index 0000000..b6238ab --- /dev/null +++ b/roles/system/tasks/snapshot/snapper.yml @@ -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: snapshot/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 btrfs subvolumes manually diff --git a/roles/system/tasks/snapshot/zrepl.yml b/roles/system/tasks/snapshot/zrepl.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/system/tasks/snapshot/zrepl.yml @@ -0,0 +1 @@ +--- diff --git a/roles/system/tasks/tlp.yml b/roles/system/tasks/tlp.yml new file mode 100644 index 0000000..03b1e5d --- /dev/null +++ b/roles/system/tasks/tlp.yml @@ -0,0 +1,27 @@ +--- +- name: tlp | Install tlp package + community.general.apk: + name: tlp + state: present + +# I have a ThinkPad laptop :) +- name: tlp | Install acpi_call kernel module + community.general.apk: + name: acpi_call-src + state: present + when: ansible_hostname == 'alpine-tp' + +- name: tlp | Copy configuration + template: + src: tlp/00-custom.j2 + dest: /etc/tlp.d/00-custom.conf + owner: root + group: root + mode: '644' + +- name: tlp | Add tlp service to runlevel 'default' + service: + name: tlp + enabled: true + state: started + runlevel: default diff --git a/roles/system/tasks/usbguard.yml b/roles/system/tasks/usbguard.yml new file mode 100644 index 0000000..594cc50 --- /dev/null +++ b/roles/system/tasks/usbguard.yml @@ -0,0 +1,47 @@ +--- +- name: usbguard | Install usbguard + community.general.apk: + name: usbguard + state: present + +- name: usbguard | Allow normal user to control policy via IPC + lineinfile: + path: /etc/usbguard/usbguard-daemon.conf + regexp: '^IPCAllowedUsers=' + line: 'IPCAllowedUsers=root {{ username }}' + state: present + owner: root + group: root + mode: '600' + +- name: usbguard | Start usbguard service on runlevel 'default' + service: + name: usbguard + runlevel: default + enabled: true + state: started + +- name: usbguard | Check whether there are defined policies + stat: + path: /etc/usbguard/rules.conf + register: have_policies + +# Or else you will be locked out from your desktop with no keyboards and mice +- name: usbguard | Generate policies for currently connected devices + shell: /usr/bin/usbguard generate-policy > /etc/usbguard/rules.conf + args: + removes: /usr/bin/usbguard + when: have_policies.stat.size == 0 + +- name: usbguard | Ensure correct permissions for /etc/usbguard/rules.conf + file: + path: /etc/usbguard/rules.conf + owner: root + group: root + mode: '600' + +- name: usbguard | Restart usbguard service to apply appended policies + service: + name: usbguard + runlevel: default + state: restarted diff --git a/roles/system/tasks/user.yml b/roles/system/tasks/user.yml new file mode 100644 index 0000000..e41f02f --- /dev/null +++ b/roles/system/tasks/user.yml @@ -0,0 +1,49 @@ +--- +- name: user | Install {{ usershell }} + community.general.apk: + name: '{{ usershell }}' + state: present + when: usershell not in ['ash', 'sh'] + +- name: user | Create a normal user + user: + name: '{{ username }}' + password: '{{ password | password_hash("sha512") }}' + update_password: on_create + append: true + groups: '{{ usergroups }}' + create_home: true + home: '/home/{{ username }}' + shell: '{{ shells_mappings[usershell] }}' + state: present + comment: Kawaii Linux user + +- name: user | Double check the existence of group '{{ username }}' + group: + name: '{{ username }}' + state: present + +# We restrict /proc read permission to polkitd group +- name: user | Add the user to polkitd group + user: + name: '{{ username }}' + append: true + groups: + - polkitd + when: use_polkit or (seat_manager == 'elogind') + +- name: user | Add the user to seat group + user: + name: '{{ username }}' + append: true + groups: + - seat + when: seat_manager == 'seatd' + +- name: user | Install {{ sudo_provider }} + community.general.apk: + name: '{{ sudo_provider }}' + state: present + +- name: user | Configure privilege escalation rules + include_tasks: 'user/{{ sudo_provider }}.yml' diff --git a/roles/system/tasks/user/doas.yml b/roles/system/tasks/user/doas.yml new file mode 100644 index 0000000..a1f627a --- /dev/null +++ b/roles/system/tasks/user/doas.yml @@ -0,0 +1,17 @@ +--- +# pm-suspend is from pm-utils package (required by libvirt-client) +- name: user | Add doas config for user {{ username }} + blockinfile: + path: /etc/doas.conf + block: | + permit persist :wheel + {% if nopasswd_commands | length > 0 %} + {% for command in nopasswd_commands %} + permit nopass {{ username }} cmd {{ command }} + {% endfor %} + {% endif %} + marker: '# {mark} ANSIBLE MANAGED SETTINGS' + owner: root + group: root + mode: '600' + validate: /usr/bin/doas -C %s diff --git a/roles/system/tasks/user/please.yml b/roles/system/tasks/user/please.yml new file mode 100644 index 0000000..5f3e24f --- /dev/null +++ b/roles/system/tasks/user/please.yml @@ -0,0 +1,49 @@ +--- +# This allows the validation below to pass +- name: user | Ensure /etc/please.ini exists + file: + path: /etc/please.ini + mode: '600' + owner: root + group: root + state: touch + +- name: user | Configure please's privilege escalation rules + blockinfile: + path: /etc/please.ini + block: | + [wheel_run_as_anyone] + name=wheel + group=true + target=^.*$ + regex=^.*$ + require_pass=true + + [wheel_edit_anything] + name=wheel + group=true + target=root + type=edit + regex=^.*$ + require_pass=true + + [wheel_list_rules] + name=wheel + group=true + target=^.*$ + type=list + require_pass=false + {% if nopasswd_commands | length > 0 %} + + [{{ username }}_run_nopasswd] + name={{ username }} + target=root + regex=^((/usr(/local)?)?/s?bin/)?{{ '(' ~ (nopasswd_commands | list | join('|')) ~ ')' }}(\s+.*)?$ + require_pass=false + {% endif %} + marker: ; {mark} ANSIBLE MANAGED SETTINGS + validate: /usr/bin/please --check %s + mode: '600' + owner: root + group: root + state: present diff --git a/roles/system/tasks/user/sudo.yml b/roles/system/tasks/user/sudo.yml new file mode 100644 index 0000000..88df557 --- /dev/null +++ b/roles/system/tasks/user/sudo.yml @@ -0,0 +1,23 @@ +--- +- name: user | Allow wheel group to run commands as root + community.general.sudoers: + name: allow-wheel-group + group: wheel + commands: ALL + host: ALL + runas: ALL:ALL + validation: required + nopassword: false + state: present + +- name: user | Allow running commands as root without password for user {{ username }} + community.general.sudoers: + name: allow-{{ username }}-user + user: '{{ username }}' + commands: '{{ nopasswd_commands | list }}' + host: ALL + runas: ALL:ALL + validation: required + nopassword: true + state: present + when: nopasswd_commands | length > 0 diff --git a/roles/system/tasks/zram.yml b/roles/system/tasks/zram.yml new file mode 100644 index 0000000..89ac5a6 --- /dev/null +++ b/roles/system/tasks/zram.yml @@ -0,0 +1,31 @@ +--- +- name: zram | Configure zram devices + template: + src: zram/zram.j2 + dest: /etc/modprobe.d/zram.conf + owner: root + group: root + mode: '644' + +- name: zram | Add zram.start hook for local service + template: + src: zram/zram-start.j2 + dest: /etc/local.d/zram.start + owner: root + group: root + mode: '755' + +- name: zram | Add zram.stop hook for local service + template: + src: zram/zram-stop.j2 + dest: /etc/local.d/zram.stop + owner: root + group: root + mode: '755' + +- name: zram | Add local service to runlevel 'default' + service: + name: local + runlevel: default + enabled: true + state: started diff --git a/roles/system/templates/auditd/audit.rules.j2 b/roles/system/templates/auditd/audit.rules.j2 new file mode 100644 index 0000000..62df1ed --- /dev/null +++ b/roles/system/templates/auditd/audit.rules.j2 @@ -0,0 +1,256 @@ +# /etc/audit/audit.rules +# Adapted from https://github.com/Neo23x0/auditd/blob/master/audit.rules +# Ref: +# - auditctl(8) +# - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-defining_audit_rules_and_controls + +# Remove any existing rules +-D + +# Buffer Size +## Feel free to increase this if the machine panic's +-b 8192 + +# Failure Mode +## Possible values: 0 (silent), 1 (printk, print a failure message), 2 (panic, halt the system) +-f 1 + +# Ignore errors +## e.g. caused by users or files not found in the local environment +-i + +# Self Auditing --------------------------------------------------------------- + +## Audit the audit logs +### Successful and unsuccessful attempts to read information from the audit records +-a always,exit -F arch=b64 -F dir=/var/log/audit/ -F perm=wra -F key=auditlog + +## Auditd configuration +### Modifications to audit configuration that occur while the audit collection functions are operating +-a always,exit -F arch=b64 -F dir=/etc/audit/ -F perm=wa -F key=auditconfig +-a always,exit -F arch=b64 -F path=/etc/libaudit.conf -F perm=wa -F key=auditconfig + +## Monitor for use of audit management tools +-a always,exit -F arch=b64 -F path=/usr/sbin/auditctl -F perm=x -F key=audittools +-a always,exit -F arch=b64 -F path=/usr/sbin/auditd -F perm=x -F key=audittools +-a always,exit -F arch=b64 -F path=/usr/sbin/augenrules -F perm=x -F key=audittools + +## Access to all audit trails +-a always,exit -F arch=b64 -F path=/usr/sbin/ausearch -F perm=x -F key=auditlog_local_access +-a always,exit -F arch=b64 -F path=/usr/sbin/aureport -F perm=x -F key=auditlog_local_access +-a always,exit -F arch=b64 -F path=/usr/bin/aulast -F perm=x -F key=auditlog_local_access +-a always,exit -F arch=b64 -F path=/usr/bin/aulastlog -F perm=x -F key=auditlog_local_access +-a always,exit -F arch=b64 -F path=/usr/bin/auvirt -F perm=x -F key=auditlog_local_access + +# Filters --------------------------------------------------------------------- + +### We put these early because audit is a first match wins system. + +## Ignore current working directory records +-a always,exclude -F msgtype=CWD + +## This is not very interesting and wastes a lot of space if the server is public facing +-a always,exclude -F msgtype=CRYPTO_KEY_USER + +# This prevents ntpd daemons from overwhelming the logs +-a never,exit -F arch=b64 -S adjtimex -F auid=unset -F uid=ntp +{% if ntp_client == 'chrony' %} +-a never,exit -F arch=b64 -S adjtimex -F auid=unset -F uid=chrony +{% endif %} + +## High Volume Event Filter (especially on Linux Workstations) +-a never,exit -F arch=b64 -F dir=/dev/shm/ -F key=sharedmemaccess + +# Rules ----------------------------------------------------------------------- + +## Kernel parameters +-a always,exit -F arch=b64 -F path=/etc/sysctl.conf -F perm=wa -F key=sysctl +-a always,exit -F arch=b64 -F dir=/etc/sysctl.d/ -F perm=wa -F key=sysctl + +# Kernel module loading and unloading +-a always,exit -F arch=b64 -F perm=x -F auid!=-1 -F path=/sbin/insmod -F key=modules +-a always,exit -F arch=b64 -F perm=x -F auid!=-1 -F path=/sbin/modprobe -F key=modules +-a always,exit -F arch=b64 -F perm=x -F auid!=-1 -F path=/sbin/rmmod -F key=modules +-a always,exit -F arch=b64 -S finit_module,init_module,delete_module -F auid!=-1 -F key=modules + +## Modprobe configuration +-a always,exit -F arch=b64 -F path=/etc/modprobe.conf -F perm=wa -F key=modprobe +-a always,exit -F arch=b64 -F dir=/etc/modprobe.d/ -F perm=wa -F key=modprobe + +## KExec usage (all actions) +-a always,exit -F arch=b64 -S kexec_load -F key=KEXEC + +## Special files +-a always,exit -F arch=b64 -S mknod,mknodat -F key=specialfiles + +## Mount operations (only attributable) +-a always,exit -F arch=b64 -S mount,umount2 -F auid!=-1 -F key=mount + +## Change swap (only attributable) +-a always,exit -F arch=b64 -S swapon,swapoff -F auid!=-1 -F key=swap + +## Time +-a always,exit -F arch=b64 -F uid!=ntp -S adjtimex,settimeofday,clock_settime -F key=time +### Local time zone +-a always,exit -F arch=b64 -F path=/etc/localtime -F perm=wa -F key=localtime + +## Cron configuration & scheduled jobs +-a always,exit -F arch=b64 -F dir=/etc/crontabs/ -F perm=wa -F key=cron +-a always,exit -F arch=b64 -F dir=/var/spool/cron/ -F perm=wa -F key=cron +-a always,exit -F arch=b64 -F dir=/etc/periodic/ -F perm=wa -F key=cron +{% if crond_provider == 'cronie' %} +-a always,exit -F arch=b64 -F dir=/etc/cron.d/ -F perm=wa -F key=cron +-a always,exit -F arch=b64 -F path=/etc/cron.allow -F perm=wa -F key=cron +-a always,exit -F arch=b64 -F path=/etc/cron.deny -F perm=wa -F key=cron +{% endif %} +{% if crond_provider == 'fcron' %} +-a always,exit -F arch=b64 -F dir=/etc/fcron/ -F perm=wa -F key=cron +-a always,exit -F arch=b64 -F dir=/var/spool/fcron/ -F perm=wa -F key=cron +{% endif %} + +## User, group, password databases +-a always,exit -F arch=b64 -F path=/etc/group -F perm=wa -F key=etcgroup +-a always,exit -F arch=b64 -F path=/etc/passwd -F perm=wa -F key=etcpasswd +-a always,exit -F arch=b64 -F path=/etc/shadow -F perm=wa -F key=etcpasswd + +# Changes to the privilege escalation programs' configurations +-a always,exit -F arch=b64 -F path=/etc/doas.conf -F perm=wa -F key=actions +-a always,exit -F arch=b64 -F path=/etc/please.ini -F perm=wa -F key=actions +-a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -F key=actions +-a always,exit -F arch=b64 -F dir=/etc/sudoers.d/ -F perm=wa -F key=actions + +## Passwd +-a always,exit -F arch=b64 -F path=/usr/bin/passwd -F perm=x -F key=passwd_modification + +## Tools to change group identifiers +-a always,exit -F arch=b64 -F path=/usr/sbin/addgroup -F perm=x -F key=group_modification +-a always,exit -F arch=b64 -F path=/usr/sbin/adduser -F perm=x -F key=user_modification +-a always,exit -F arch=b64 -F path=/usr/sbin/delgroup -F perm=x -F key=user_modification +-a always,exit -F arch=b64 -F path=/usr/sbin/deluser -F perm=x -F key=user_modification + +## Login configuration and information +-a always,exit -F arch=b64 -F path=/etc/securetty -F perm=wa -F key=login + +## Network Environment +### Changes to hostname +-a always,exit -F arch=b64 -S sethostname,setdomainname -F key=network_modifications + +### Successful IPv4 Connections +-a always,exit -F arch=b64 -S connect -F a2=16 -F success=1 -F key=network_connect_4 + +### Successful IPv6 Connections +-a always,exit -F arch=b64 -S connect -F a2=28 -F success=1 -F key=network_connect_6 + +### Changes to other files +-a always,exit -F arch=b64 -F path=/etc/hosts -F perm=wa -F key=network_modifications +-a always,exit -F arch=b64 -F path=/etc/netconfig -F perm=wa -F key=network_modifications +-a always,exit -F arch=b64 -F dir=/etc/network/ -F perm=wa -F key=network + +### Changes to issue +-a always,exit -F arch=b64 -F path=/etc/issue -F perm=wa -F key=etcissue + +## System startup scripts and service configurations +-a always,exit -F arch=b64 -F path=/etc/inittab -F perm=wa -F key=init +-a always,exit -F arch=b64 -F dir=/etc/init.d/ -F perm=wa -F key=init +-a always,exit -F arch=b64 -F dir=/etc/conf.d/ -F perm=wa -F key=init + +## Pam configuration +-a always,exit -F arch=b64 -F dir=/etc/pam.d/ -F perm=wa -F key=pam +-a always,exit -F arch=b64 -F path=/etc/security/limits.conf -F perm=wa -F key=pam +-a always,exit -F arch=b64 -F path=/etc/security/limits.d -F perm=wa -F key=pam +-a always,exit -F arch=b64 -F path=/etc/security/pam_env.conf -F perm=wa -F key=pam +-a always,exit -F arch=b64 -F path=/etc/security/namespace.conf -F perm=wa -F key=pam +-a always,exit -F arch=b64 -F path=/etc/security/namespace.d -F perm=wa -F key=pam +-a always,exit -F arch=b64 -F path=/etc/security/namespace.init -F perm=wa -F key=pam + +## Critical elements access failures +-a always,exit -F arch=b64 -S open -F dir=/etc/ -F success=0 -F key=unauthedfileaccess +-a always,exit -F arch=b64 -S open -F dir=/bin/ -F success=0 -F key=unauthedfileaccess +-a always,exit -F arch=b64 -S open -F dir=/sbin/ -F success=0 -F key=unauthedfileaccess +-a always,exit -F arch=b64 -S open -F dir=/usr/bin/ -F success=0 -F key=unauthedfileaccess +-a always,exit -F arch=b64 -S open -F dir=/usr/sbin/ -F success=0 -F key=unauthedfileaccess +-a always,exit -F arch=b64 -S open -F dir=/var/ -F success=0 -F key=unauthedfileaccess +-a always,exit -F arch=b64 -S open -F dir=/home/ -F success=0 -F key=unauthedfileaccess + +## Process ID change (switching accounts) applications +-a always,exit -F arch=b64 -F path=/bin/su -F perm=x -F key=priv_esc +-a always,exit -F arch=b64 -F path=/usr/bin/doas -F perm=x -F key=priv_esc +-a always,exit -F arch=b64 -F path=/usr/bin/please -F perm=x -F key=priv_esc +-a always,exit -F arch=b64 -F path=/usr/bin/pleaseedit -F perm=x -F key=priv_esc +-a always,exit -F arch=b64 -F path=/usr/bin/sudo -F perm=x -F key=priv_esc +-a always,exit -F arch=b64 -F path=/usr/bin/sudoedit -F perm=x -F key=priv_esc + +## Power state +-a always,exit -F arch=b64 -F path=/sbin/poweroff -F perm=x -F key=power +-a always,exit -F arch=b64 -F path=/sbin/reboot -F perm=x -F key=power +-a always,exit -F arch=b64 -F path=/sbin/halt -F perm=x -F key=power + +## Session initiation information +-a always,exit -F arch=b64 -F dir=/var/log/swtpm/ -F perm=wa -F key=session + +# Special Rules --------------------------------------------------------------- + +## dbus-send invocation +### may indicate privilege escalation CVE-2021-3560 +-a always,exit -F arch=b64 -F path=/usr/bin/dbus-send -F perm=x -F key=dbus_send +-a always,exit -F arch=b64 -F path=/usr/bin/gdbus -F perm=x -F key=gdubs_call + +## pkexec invocation +### may indicate privilege escalation CVE-2021-4034 +-a always,exit -F arch=b64 -F path=/usr/bin/pkexec -F perm=x -F key=pkexec + +## Injection +### These rules watch for code injection by the ptrace facility. +### This could indicate someone trying to do something bad or just debugging +-a always,exit -F arch=b64 -S ptrace -F a0=0x4 -F key=code_injection +-a always,exit -F arch=b64 -S ptrace -F a0=0x5 -F key=data_injection +-a always,exit -F arch=b64 -S ptrace -F a0=0x6 -F key=register_injection +-a always,exit -F arch=b64 -S ptrace -F key=tracing + +## Anonymous File Creation +### These rules watch the use of memfd_create +### "memfd_create" creates anonymous file and returns a file descriptor to access it +### When combined with "fexecve" can be used to stealthily run binaries in memory without touching disk +-a always,exit -F arch=b64 -S memfd_create -F key=anon_file_create + +## Privilege Abuse +### The purpose of this rule is to detect when an admin may be abusing power by looking in user's home dir. +-a always,exit -F dir=/home/ -F auid=0 -F auid>=1000 -F auid!=-1 -F key=power_abuse + +# Socket Creations +# will catch both IPv4 and IPv6 +-a always,exit -F arch=b64 -S socket -F a0=2 -F key=exfiltration_over_other_network_medium +-a always,exit -F arch=b64 -S socket -F a0=10 -F key=exfiltration_over_other_network_medium + +# Software Management --------------------------------------------------------- +-a always,exit -F arch=b64 -F path=/usr/bin/flatpak -F perm=x -F key=software_mgmt +-a always,exit -F arch=b64 -F path=/sbin/apk -F perm=x -F key=software_mgmt +-a always,exit -F arch=b64 -F dir=/etc/apk/ -F perm=wa -F key=software_mgmt + +# High Volume Events ---------------------------------------------------------- + +## Disable these rules if they create too many events in your environment + +## File Access +### Unauthorized Access (unsuccessful) +-a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=-1 -F key=file_access +-a always,exit -F arch=b64 -S creat,open,openat,open_by_handle_at,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=-1 -F key=file_access + +### Unsuccessful Creation +-a always,exit -F arch=b64 -S mkdir,creat,link,symlink,mknod,mknodat,linkat,symlinkat -F exit=-EACCES -F key=file_creation +-a always,exit -F arch=b64 -S mkdir,link,symlink,mkdirat -F exit=-EPERM -F key=file_creation + +### Unsuccessful Modification +-a always,exit -F arch=b64 -S rename,renameat,truncate,chmod,setxattr,lsetxattr,removexattr,lremovexattr -F exit=-EACCES -F key=file_modification +-a always,exit -F arch=b64 -S rename,renameat,truncate,chmod,setxattr,lsetxattr,removexattr,lremovexattr -F exit=-EPERM -F key=file_modification + +## 32bit API Exploitation +### If you are on a 64 bit platform, everything _should_ be running +### in 64 bit mode. This rule will detect any use of the 32 bit syscalls +### because this might be a sign of someone exploiting a hole in the 32 +### bit API. +-a always,exit -F arch=b32 -S all -F key=32bit_api + +# Make The Configuration Immutable -------------------------------------------- + +##-e 2 diff --git a/roles/system/templates/devd/51-android.j2 b/roles/system/templates/devd/51-android.j2 new file mode 100644 index 0000000..6212153 --- /dev/null +++ b/roles/system/templates/devd/51-android.j2 @@ -0,0 +1,2 @@ +# Galaxy A5 2016 (a5xelte) +SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", ATTR{idProduct}=="6860", MODE="0660", OWNER="{{ username }}", ENV{ID_MTP_DEVICE}="1", SYMLINK+="libmtp" diff --git a/roles/system/templates/devd/70-uinput-wheel.j2 b/roles/system/templates/devd/70-uinput-wheel.j2 new file mode 100644 index 0000000..dec9c3f --- /dev/null +++ b/roles/system/templates/devd/70-uinput-wheel.j2 @@ -0,0 +1 @@ +KERNEL=="uinput", MODE="0660", OWNER="{{ username }}", OPTIONS+="static_node=uinput" diff --git a/roles/system/templates/devd/70-xp-pen.j2 b/roles/system/templates/devd/70-xp-pen.j2 new file mode 100644 index 0000000..f67897b --- /dev/null +++ b/roles/system/templates/devd/70-xp-pen.j2 @@ -0,0 +1 @@ +SUBSYSTEM=="usb", ATTR{idVendor}=="28bd", OWNERS="{{ username }}", TAG+="uaccess" diff --git a/roles/system/templates/devd/99-calibrate.j2 b/roles/system/templates/devd/99-calibrate.j2 new file mode 100644 index 0000000..b752191 --- /dev/null +++ b/roles/system/templates/devd/99-calibrate.j2 @@ -0,0 +1 @@ +ENV{ID_VENDOR_ID}=="10429", ENV{ID_MODEL_ID}=="63754", ENV{WL_OUTPUT}="HDMI-A-1", ENV{LIBINPUT_CALIBRATION_MATRIX}="1 0 0 0 1 0" diff --git a/roles/system/templates/dns/dnscrypt-proxy.j2 b/roles/system/templates/dns/dnscrypt-proxy.j2 new file mode 100644 index 0000000..83b09e5 --- /dev/null +++ b/roles/system/templates/dns/dnscrypt-proxy.j2 @@ -0,0 +1,763 @@ +################################## +# Global settings # +################################## + +## List of servers to use +## +## Servers from the "public-resolvers" source (see down below) can +## be viewed here: https://dnscrypt.info/public-servers +## +## The proxy will automatically pick working servers from this list. +## Note that the require_* filters do NOT apply when using this setting. +## +## By default, this list is empty and all registered servers matching the +## require_* filters will be used instead. +## +## Remove the leading # first to enable this; lines starting with # are ignored. + +server_names = [{{ dnscrypt.server_names | map('quote_single') | join(', ') }}] + + +## List of local addresses and ports to listen to. Can be IPv4 and/or IPv6. +## Example with both IPv4 and IPv6: +## listen_addresses = ['127.0.0.1:53', '[::1]:53'] +## +## To listen to all IPv4 addresses, use `listen_addresses = ['0.0.0.0:53']` +## To listen to all IPv4+IPv6 addresses, use `listen_addresses = ['[::]:53']` + +listen_addresses = ['127.0.0.1:53', '[::1]:53'] + + +## Maximum number of simultaneous client connections to accept + +max_clients = 250 + + +## Switch to a different system user after listening sockets have been created. +## Note (1): this feature is currently unsupported on Windows. +## Note (2): this feature is not compatible with systemd socket activation. +## Note (3): when using -pidfile, the PID file directory must be writable by the new user + +# user_name = 'dnscrypt' + + +## Require servers (from remote sources) to satisfy specific properties + +# Use servers reachable over IPv4 +ipv4_servers = true + +# Use servers reachable over IPv6 -- Do not enable if you don't have IPv6 connectivity +ipv6_servers = true + +# Use servers implementing the DNSCrypt protocol +dnscrypt_servers = true + +# Use servers implementing the DNS-over-HTTPS protocol +doh_servers = {{ dnscrypt.anonymized_dns.enabled | ternary('false', 'true') }} + +# Use servers implementing the Oblivious DoH protocol +odoh_servers = {{ dnscrypt.anonymized_dns.enabled | ternary('false', 'true') }} + + +## Require servers defined by remote sources to satisfy specific properties + +# Server must support DNS security extensions (DNSSEC) +require_dnssec = true + +# Server must not log user queries (declarative) +require_nolog = true + +# Server must not enforce its own blocklist (for parental control, ads blocking...) +require_nofilter = false + +# Server names to avoid even if they match all criteria +disabled_server_names = [] + + +## Always use TCP to connect to upstream servers. +## This can be useful if you need to route everything through Tor. +## Otherwise, leave this to `false`, as it doesn't improve security +## (dnscrypt-proxy will always encrypt everything even using UDP), and can +## only increase latency. + +force_tcp = false + + +## SOCKS proxy +## Uncomment the following line to route all TCP connections to a local Tor node +## Tor doesn't support UDP, so set `force_tcp` to `true` as well. + +# proxy = 'socks5://127.0.0.1:9050' + + +## HTTP/HTTPS proxy +## Only for DoH servers + +# http_proxy = 'http://127.0.0.1:8888' + + +## How long a DNS query will wait for a response, in milliseconds. +## If you have a network with *a lot* of latency, you may need to +## increase this. Startup may be slower if you do so. +## Don't increase it too much. 10000 is the highest reasonable value. + +timeout = 5000 + + +## Keepalive for HTTP (HTTPS, HTTP/2) queries, in seconds + +keepalive = 30 + + +## Add EDNS-client-subnet information to outgoing queries +## +## Multiple networks can be listed; they will be randomly chosen. +## These networks don't have to match your actual networks. + +# edns_client_subnet = ['0.0.0.0/0', '2001:db8::/32'] + + +## Response for blocked queries. Options are `refused`, `hinfo` (default) or +## an IP response. To give an IP response, use the format `a:,aaaa:`. +## Using the `hinfo` option means that some responses will be lies. +## Unfortunately, the `hinfo` option appears to be required for Android 8+ + +blocked_query_response = 'refused' + + +## Load-balancing strategy: 'p2' (default), 'ph', 'p', 'first' or 'random' +## Randomly choose 1 of the fastest 2, half, n, 1 or all live servers by latency. +## The response quality still depends on the server itself. + +lb_strategy = 'p2' + +## Set to `true` to constantly try to estimate the latency of all the resolvers +## and adjust the load-balancing parameters accordingly, or to `false` to disable. +## Default is `true` that makes 'p2' `lb_strategy` work well. + +lb_estimator = true + + +## Log level (0-6, default: 2 - 0 is very verbose, 6 only contains fatal errors) + +log_level = 2 + + +## Log file for the application, as an alternative to sending logs to +## the standard system logging service (syslog/Windows event log). +## +## This file is different from other log files, and will not be +## automatically rotated by the application. + +# log_file = '/var/log/dnscrypt-proxy/dnscrypt-proxy.log' + + +## When using a log file, only keep logs from the most recent launch. + +# log_file_latest = true + + +## Use the system logger (syslog on Unix, Event Log on Windows) + +use_syslog = true + + +## Delay, in minutes, after which certificates are reloaded + +cert_refresh_delay = 240 + + +## DNSCrypt: Create a new, unique key for every single DNS query +## 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 = true + + +## DoH: Disable TLS session tickets - increases privacy but also latency + +tls_disable_session_tickets = true + + +## DoH: Use a specific cipher suite instead of the server preference +## 49199 = TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +## 49195 = TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 +## 52392 = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 +## 52393 = TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 +## 4865 = TLS_AES_128_GCM_SHA256 +## 4867 = TLS_CHACHA20_POLY1305_SHA256 +## +## On non-Intel CPUs such as MIPS routers and ARM systems (Android, Raspberry Pi...), +## the following suite improves performance. +## This may also help on Intel CPUs running 32-bit operating systems. +## +## 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 = [52392, 49199] + + +## Bootstrap resolvers +## +## These are normal, non-encrypted DNS resolvers, that will be only used +## for one-shot queries when retrieving the initial resolvers list and the +## the system DNS configuration doesn't work. +## +## No user queries will ever be leaked through these resolvers, and they will +## not be used after IP addresses of DoH resolvers have been found (if you are +## using DoH). +## +## They will never be used if lists have already been cached, and if the stamps +## of the configured servers already include IP addresses (which is the case for +## most of DoH servers, and for all DNSCrypt servers and relays). +## +## They will not be used if the configured system DNS works, or after the +## proxy already has at least one usable secure resolver. +## +## Resolvers supporting DNSSEC are recommended, and, if you are using +## DoH, bootstrap resolvers should ideally be operated by a different entity +## than the DoH servers you will be using, especially if you have IPv6 enabled. +## +## People in China may want to use 114.114.114.114:53 here. +## Other popular options include 8.8.8.8, 9.9.9.9 and 1.1.1.1. +## +## If more than one resolver is specified, they will be tried in sequence. +## +## TL;DR: put valid standard resolver addresses here. Your actual queries will +## not be sent there. If you're using DNSCrypt or Anonymized DNS and your +## lists are up to date, these resolvers will not even be used. + +bootstrap_resolvers = {{ dnscrypt.bootstrap_resolvers }} + + +## Always use the bootstrap resolver before the system DNS settings. + +ignore_system_dns = true + + +## Maximum time (in seconds) to wait for network connectivity before +## initializing the proxy. +## Useful if the proxy is automatically started at boot, and network +## connectivity is not guaranteed to be immediately available. +## Use 0 to not test for connectivity at all (not recommended), +## and -1 to wait as much as possible. + +netprobe_timeout = 60 + +## Address and port to try initializing a connection to, just to check +## if the network is up. It can be any address and any port, even if +## there is nothing answering these on the other side. Just don't use +## a local address, as the goal is to check for Internet connectivity. +## On Windows, a datagram with a single, nul byte will be sent, only +## when the system starts. +## On other operating systems, the connection will be initialized +## but nothing will be sent at all. + +netprobe_address = '{{ dnscrypt.netprobe_address }}' + + +## Offline mode - Do not use any remote encrypted servers. +## The proxy will remain fully functional to respond to queries that +## plugins can handle directly (forwarding, cloaking, ...) + +offline_mode = false + + +## Additional data to attach to outgoing queries. +## These strings will be added as TXT records to queries. +## Do not use, except on servers explicitly asking for extra data +## to be present. +## encrypted-dns-server can be configured to use this for access control +## in the [access_control] section + +# query_meta = ['key1:value1', 'key2:value2', 'token:MySecretToken'] + + +## Automatic log files rotation + +# Maximum log files size in MB - Set to 0 for unlimited. +log_files_max_size = 10 + +# How long to keep backup files, in days +log_files_max_age = 7 + +# Maximum log files backups to keep (or 0 to keep all backups) +log_files_max_backups = 1 + + + +######################### +# Filters # +######################### + +## Note: if you are using dnsmasq, disable the `dnssec` option in dnsmasq if you +## configure dnscrypt-proxy to do any kind of filtering (including the filters +## below and blocklists). +## You can still choose resolvers that do DNSSEC validation. + + +## Immediately respond to IPv6-related queries with an empty response +## This makes things faster when there is no IPv6 connectivity, but can +## also cause reliability issues with some stub resolvers. + +block_ipv6 = false + + +## Immediately respond to A and AAAA queries for host names without a domain name + +block_unqualified = true + + +## Immediately respond to queries for local zones instead of leaking them to +## upstream resolvers (always causing errors or timeouts). + +block_undelegated = true + + +## TTL for synthetic responses sent when a request has been blocked (due to +## IPv6 or blocklists). + +reject_ttl = 10 + + + +################################################################################## +# Route queries for specific domains to a dedicated set of servers # +################################################################################## + +## See the `/usr/share/dnscrypt-proxy/example-forwarding-rules.txt` file for an example + +# forwarding_rules = '/etc/dnscrypt-proxy/forwarding-rules.txt' + + + +############################### +# Cloaking rules # +############################### + +## Cloaking returns a predefined address for a specific name. +## In addition to acting as a HOSTS file, it can also return the IP address +## of a different name. It will also do CNAME flattening. +## +## See the `/usr/share/dnscrypt-proxy/example-cloaking-rules.txt` file for an example + +cloaking_rules = '/etc/dnscrypt-proxy/cloaking-rules.txt' + +## TTL used when serving entries in cloaking-rules.txt + +cloak_ttl = 600 + + + +########################### +# DNS cache # +########################### + +## Enable a DNS cache to reduce latency and outgoing traffic + +cache = true + + +## Cache size + +cache_size = 4096 + + +## Minimum TTL for cached entries + +cache_min_ttl = 2400 + + +## Maximum TTL for cached entries + +cache_max_ttl = 86400 + + +## Minimum TTL for negatively cached entries + +cache_neg_min_ttl = 60 + + +## Maximum TTL for negatively cached entries + +cache_neg_max_ttl = 600 + + + +######################################## +# Captive portal handling # +######################################## + +[captive_portals] + +## A file that contains a set of names used by operating systems to +## check for connectivity and captive portals, along with hard-coded +## IP addresses to return. +## see '/usr/share/dnscrypt-proxy/example-captive-portals.txt' file for an example + +# map_file = '/etc/dnscrypt-proxy/captive-portals.txt' + + + +################################## +# Local DoH server # +################################## + +[local_doh] + +{% if dnscrypt.local_doh.enabled %} +## dnscrypt-proxy can act as a local DoH server. By doing so, web browsers +## requiring a direct connection to a DoH server in order to enable some +## features will enable these, without bypassing your DNS proxy. + +## Addresses that the local DoH server should listen to + +listen_addresses = {{ dnscrypt.local_doh.listen_addresses }} + + +## Path of the DoH URL. This is not a file, but the part after the hostname +## in the URL. By convention, `/dns-query` is frequently chosen. +## For each `listen_address` the complete URL to access the server will be: +## `https://` (ex: `https://127.0.0.1/dns-query`) + +path = '{{ dnscrypt.local_doh.path }}' + + +## Certificate file and key - Note that the certificate has to be trusted. +## See the documentation (wiki) for more information. + +cert_file = '{{ ansible_hostname }}.crt' +cert_key_file = '{{ ansible_hostname }}.pem' +{% endif %} + + + +############################### +# Query logging # +############################### + +## Log client queries to a file + +[query_log] + + ## Path to the query log file (absolute, or relative to the same directory as the config file) + ## Can be set to /dev/stdout in order to log to the standard output. + + file = '/var/log/dnscrypt-proxy/query.log' + + + ## Query log format (currently supported: tsv and ltsv) + + format = 'tsv' + + + ## Do not log these query types, to reduce verbosity. Keep empty to log everything. + + ignored_qtypes = ['DNSKEY', 'NS'] + + + +############################################ +# Suspicious queries logging # +############################################ + +## Log queries for nonexistent zones +## These queries can reveal the presence of malware, broken/obsolete applications, +## and devices signaling their presence to 3rd parties. + +[nx_log] + + ## Path to the query log file (absolute, or relative to the same directory as the config file) + + file = '/var/log/dnscrypt-proxy/nx.log' + + + ## Query log format (currently supported: tsv and ltsv) + + format = 'tsv' + + + +###################################################### +# Pattern-based blocking (blocklists) # +###################################################### + +## Blocklists are made of one pattern per line. Example of valid patterns: +## +## example.com +## =example.com +## *sex* +## ads.* +## ads*.example.* +## ads*.example[0-9]*.com +## +## Example blocklist files can be found at https://download.dnscrypt.info/blocklists/ +## A script to build blocklists from public feeds can be found in the +## `utils/generate-domains-blocklists` directory of the dnscrypt-proxy source code. + +[blocked_names] + +{% 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' + + + ## Optional path to a file logging blocked queries + + log_file = '/var/log/dnscrypt-proxy/blocked-names.log' + + + ## Optional log format: tsv or ltsv (default: tsv) + + log_format = 'tsv' +{% endif %} + + + +########################################################### +# Pattern-based IP blocking (IP blocklists) # +########################################################### + +## IP blocklists are made of one pattern per line. Example of valid patterns: +## +## 127.* +## fe80:abcd:* +## 192.168.1.4 + +[blocked_ips] + + ## Path to the file of blocking rules (absolute, or relative to the same directory as the config file) + + blocked_ips_file = '/etc/dnscrypt-proxy/blocked-ips.txt' + + + ## Optional path to a file logging blocked queries + + log_file = '/var/log/dnscrypt-proxy/blocked-ips.log' + + + ## Optional log format: tsv or ltsv (default: tsv) + + log_format = 'tsv' + + + +###################################################### +# Pattern-based allow lists (blocklists bypass) # +###################################################### + +## Allowlists support the same patterns as blocklists +## If a name matches an allowlist entry, the corresponding session +## will bypass names and IP filters. +## +## Time-based rules are also supported to make some websites only accessible at specific times of the day. + +[allowed_names] + + ## Path to the file of allow list rules (absolute, or relative to the same directory as the config file) + + # allowed_names_file = '/etc/dnscrypt-proxy/allowed-names.txt' + + + ## Optional path to a file logging allowed queries + + # log_file = '/var/log/dnscrypt-proxy/allowed-names.log' + + + ## Optional log format: tsv or ltsv (default: tsv) + + # log_format = 'tsv' + + + +######################################################### +# Pattern-based allowed IPs lists (blocklists bypass) # +######################################################### + +## Allowed IP lists support the same patterns as IP blocklists +## If an IP response matches an allow ip entry, the corresponding session +## will bypass IP filters. +## +## Time-based rules are also supported to make some websites only accessible at specific times of the day. + +[allowed_ips] + + ## Path to the file of allowed ip rules (absolute, or relative to the same directory as the config file) + + # allowed_ips_file = '/etc/dnscrypt-proxy/allowed-ips.txt' + + + ## Optional path to a file logging allowed queries + + # log_file = '/var/log/dnscrypt-proxy/allowed-ips.log' + + ## Optional log format: tsv or ltsv (default: tsv) + + # log_format = 'tsv' + + + +########################################## +# Time access restrictions # +########################################## + +## One or more weekly schedules can be defined here. +## Patterns in the name-based blocked_names file can optionally be followed with @schedule_name +## to apply the pattern 'schedule_name' only when it matches a time range of that schedule. +## +## For example, the following rule in a blocklist file: +## *.youtube.* @time-to-sleep +## would block access to YouTube during the times defined by the 'time-to-sleep' schedule. +## +## {after='21:00', before= '7:00'} matches 0:00-7:00 and 21:00-0:00 +## {after= '9:00', before='18:00'} matches 9:00-18:00 + +[schedules] + + # [schedules.'time-to-sleep'] + # mon = [{after='21:00', before='7:00'}] + # tue = [{after='21:00', before='7:00'}] + # wed = [{after='21:00', before='7:00'}] + # thu = [{after='21:00', before='7:00'}] + # fri = [{after='23:00', before='7:00'}] + # sat = [{after='23:00', before='7:00'}] + # sun = [{after='21:00', before='7:00'}] + + # [schedules.'work'] + # mon = [{after='9:00', before='18:00'}] + # tue = [{after='9:00', before='18:00'}] + # wed = [{after='9:00', before='18:00'}] + # thu = [{after='9:00', before='18:00'}] + # fri = [{after='9:00', before='17:00'}] + + + +######################### +# Servers # +######################### + +## Remote lists of available servers +## Multiple sources can be used simultaneously, but every source +## requires a dedicated cache file. +## +## Refer to the documentation for URLs of public sources. +## +## A prefix can be prepended to server names in order to +## avoid collisions if different sources share the same for +## different servers. In that case, names listed in `server_names` +## must include the prefixes. +## +## If the `urls` property is missing, cache files and valid signatures +## must already be present. This doesn't prevent these cache files from +## expiring after `refresh_delay` hours. +## Cache freshness is checked every 24 hours, so values for 'refresh_delay' +## of less than 24 hours will have no effect. +## A maximum delay of 168 hours (1 week) is imposed to ensure cache freshness. + +[sources] +{% for source in dnscrypt_server_sources %} + + [sources.'{{ source.name }}'] + urls = [{{ source.urls | map('quote_single') | join(', ') }}] + cache_file = '{{ source.cache_file }}' + minisign_key = '{{ source.minisign_key }}' + refresh_delay = {{ source.refresh_delay }} + prefix = '{{ source.prefix }}' +{% endfor %} + + +######################################### +# Servers with known bugs # +######################################### + +[broken_implementations] + +# Cisco servers currently cannot handle queries larger than 1472 bytes, and don't +# truncate reponses larger than questions as expected by the DNSCrypt protocol. +# This prevents large responses from being received over UDP and over relays. +# +# Older versions of the `dnsdist` server software had a bug with queries larger +# than 1500 bytes. This is fixed since `dnsdist` version 1.5.0, but +# some server may still run an outdated version. +# +# The list below enables workarounds to make non-relayed usage more reliable +# until the servers are fixed. + +fragments_blocked = ['cisco', 'cisco-ipv6', 'cisco-familyshield', 'cisco-familyshield-ipv6', 'cleanbrowsing-adult', 'cleanbrowsing-adult-ipv6', 'cleanbrowsing-family', 'cleanbrowsing-family-ipv6', 'cleanbrowsing-security', 'cleanbrowsing-security-ipv6'] + + + +################################################################# +# Certificate-based client authentication for DoH # +################################################################# + +# Use a X509 certificate to authenticate yourself when connecting to DoH servers. +# This is only useful if you are operating your own, private DoH server(s). +# 'creds' maps servers to certificates, and supports multiple entries. +# If you are not using the standard root CA, an optional "root_ca" +# property set to the path to a root CRT file can be added to a server entry. + +[doh_client_x509_auth] + +# +# creds = [ +# { server_name='*', client_cert='client.crt', client_key='client.key' } +# ] + + + +################################ +# Anonymized DNS # +################################ + +[anonymized_dns] + +## Routes are indirect ways to reach DNSCrypt servers. +## +## A route maps a server name ("server_name") to one or more relays that will be +## used to connect to that server. +## +## A relay can be specified as a DNS Stamp (either a relay stamp, or a +## DNSCrypt stamp) or a server name. +## +## The following example routes "example-server-1" via `anon-example-1` or `anon-example-2`, +## and "example-server-2" via the relay whose relay DNS stamp is +## "sdns://gRIxMzcuNzQuMjIzLjIzNDo0NDM". +## +## !!! THESE ARE JUST EXAMPLES !!! +## +## Review the list of available relays from the "relays.md" file, and, for each +## server you want to use, define the relays you want connections to go through. +## +## Carefully choose relays and servers so that they are run by different entities. +## +## "server_name" can also be set to "*" to define a default route, for all servers: +## { server_name='*', via=['anon-example-1', 'anon-example-2'] } +## +## If a route is ["*"], the proxy automatically picks a relay on a distinct network. +## { server_name='*', via=['*'] } is also an option, but is likely to be suboptimal. +## +## Manual selection is always recommended over automatic selection, so that you can +## select (relay,server) pairs that work well and fit your own criteria (close by or +## in different countries, operated by different entities, on distinct ISPs...) + +{% if dnscrypt.anonymized_dns.enabled %} +routes = [ +{% for route in dnscrypt.anonymized_dns.routes %} + { server_name='{{ route.server_name }}', via=[{{ route.via | map('quote_single') | join(', ') }}]}, +{% endfor %} +] + + +# Skip resolvers incompatible with anonymization instead of using them directly + +skip_incompatible = true + + +# If public server certificates for a non-conformant server cannot be +# retrieved via a relay, try getting them directly. Actual queries +# will then always go through relays. + +direct_cert_fallback = false +{% endif %} diff --git a/roles/system/templates/dns/resolvconf.j2 b/roles/system/templates/dns/resolvconf.j2 new file mode 100644 index 0000000..75e24fb --- /dev/null +++ b/roles/system/templates/dns/resolvconf.j2 @@ -0,0 +1,10 @@ +# Configuration for resolvconf(8) +# See resolvconf.conf(5) for details + +resolv_conf=/etc/resolv.conf +# If you run a local name server, you should uncomment the below line and +# configure your subscribers configuration files below. +name_servers="127.0.0.1 ::1" +{% if dns_resolver == 'dnscrypt-proxy' %} +resolv_conf_options="edns0" +{% endif %} diff --git a/roles/system/templates/dns/unbound.j2 b/roles/system/templates/dns/unbound.j2 new file mode 100644 index 0000000..6a9f54a --- /dev/null +++ b/roles/system/templates/dns/unbound.j2 @@ -0,0 +1,396 @@ +# The server clause sets the main parameters. +server: + # verbosity number, 0 is least verbose. 1 is default. + verbosity: 0 + + # print statistics to the log (for every thread) every N seconds. + # Set to "" or 0 to disable. Default is disabled. + statistics-interval: 0 + + # enable shm for stats, default no. if you enable also enable + # statistics-interval, every time it also writes stats to the + # shared memory segment keyed with shm-key. + shm-enable: no + + # shm for stats uses this key, and key+1 for the shared mem segment. + # shm-key: 11777 + + # enable cumulative statistics, without clearing them after printing. + statistics-cumulative: no + + # enable extended statistics (query types, answer codes, status) + # printed from unbound-control. default off, because of speed. + extended-statistics: no + + # number of threads to create. 1 disables threading. + # num-threads: 1 + + # specify the interfaces to answer queries from by ip-address. + # The default is to listen to localhost (127.0.0.1 and ::1). + # specify 0.0.0.0 and ::0 to bind to all available interfaces. + # specify every interface[@port] on a new 'interface:' labelled line. + # The listen interfaces are not changed on reload, only on restart. + interface: 127.0.0.1 + interface: ::1 + + # port to answer queries from + port: 53 + + # Specify a netblock to use remainder 64 bits as random bits for + # upstream queries. Uses freebind option (Linux). + # outgoing-interface: 2001:DB8::/64 + # Also (Linux:) ip -6 addr add 2001:db8::/64 dev lo + # And: ip -6 route add local 2001:db8::/64 dev lo + # And set prefer-ip6: yes to use the ip6 randomness from a netblock. + # Set this to yes to prefer ipv6 upstream servers over ipv4. + prefer-ip6: yes + + # Prefer ipv4 upstream servers, even if ipv6 is available. + prefer-ip4: no + + # number of outgoing simultaneous tcp buffers to hold per thread. + # outgoing-num-tcp: 10 + + # number of incoming simultaneous tcp buffers to hold per thread. + # incoming-num-tcp: 10 + + # buffer size for UDP port 53 incoming (SO_RCVBUF socket option). + # 0 is system default. Use 4m to catch query spikes for busy servers. + # so-rcvbuf: 0 + + # buffer size for UDP port 53 outgoing (SO_SNDBUF socket option). + # 0 is system default. Use 4m to handle spikes on very busy servers. + # so-sndbuf: 0 + + # use SO_REUSEPORT to distribute queries over threads. + # at extreme load it could be better to turn it off to distribute even. + so-reuseport: yes + + # msec to wait before close of port on timeout UDP. 0 disables. + # delay-close: 0 + + # perform connect for UDP sockets to mitigate ICMP side channel. + udp-connect: yes + + # The number of retries when a non-positive response is received. + # outbound-msg-retry: 5 + + # msec for waiting for an unknown server to reply. Increase if you + # are behind a slow satellite link, to eg. 1128. + unknown-server-time-limit: 376 + + # the time to live (TTL) value lower bound, in seconds. Default 0. + # If more than an hour could easily give trouble due to stale data. + # cache-min-ttl: 300 + + # the time to live (TTL) value cap for RRsets and messages in the + # cache. Items are not cached for longer. In seconds. + cache-max-ttl: 86400 + + # Enable IPv4, "yes" or "no". + do-ip4: yes + + # Enable IPv6, "yes" or "no". + do-ip6: yes + + # Enable UDP, "yes" or "no". + do-udp: yes + + # Enable TCP, "yes" or "no". + do-tcp: yes + + # Use systemd socket activation for UDP, TCP, and control sockets. + use-systemd: no + + # Detach from the terminal, run in background, "yes" or "no". + # Set the value to "no" when Unbound runs as systemd service. + do-daemonize: no + + # control which clients are allowed to make (recursive) queries + # to this server. Specify classless netblocks with /size and action. + # By default everything is refused, except for localhost. + # Choose deny (drop message), refuse (polite error reply), + # allow (recursive ok), allow_setrd (recursive ok, rd bit is forced on), + # allow_snoop (recursive and nonrecursive ok) + # deny_non_local (drop queries unless can be answered from local-data) + # refuse_non_local (like deny_non_local but polite error reply). + access-control: 0.0.0.0/0 refuse + access-control: 127.0.0.0/8 allow + access-control: ::0/0 refuse + access-control: ::1/128 allow + access-control: ::ffff:127.0.0.1 allow + + # if given, a chroot(2) is done to the given directory. + # i.e. you can chroot to the working directory, for example, + # for extra security, but make sure all files are in that directory. + # + # If chroot is enabled, you should pass the configfile (from the + # commandline) as a full path from the original root. After the + # chroot has been performed the now defunct portion of the config + # file path is removed to be able to reread the config after a reload. + # + # All other file paths (working dir, logfile, roothints, and + # key files) can be specified in several ways: + # o as an absolute path relative to the new root. + # o as a relative path to the working directory. + # o as an absolute path relative to the original root. + # In the last case the path is adjusted to remove the unused portion. + # + # The pid file can be absolute and outside of the chroot, it is + # written just prior to performing the chroot and dropping permissions. + # + # Additionally, Unbound may need to access /dev/urandom (for entropy). + # How to do this is specific to your OS. + # + # If you give "" no chroot is performed. The path must not end in a /. + chroot: "/etc/unbound" + + # if given, user privileges are dropped (after binding port), + # and the given username is assumed. Default is user "unbound". + # If you give "" no privileges are dropped. + username: "unbound" + + # the working directory. The relative files in this config are + # relative to this directory. If you give "" the working directory + # is not changed. + # If you give a server: directory: dir before include: file statements + # then those includes can be relative to the working directory. + directory: "/etc/unbound" + + # the log file, "" means log to stderr. + # Use of this option sets use-syslog to "no". + logfile: "/dev/null" + + # Log to syslog(3) if yes. The log facility LOG_DAEMON is used to + # log to. If yes, it overrides the logfile. + # use-syslog: yes + + # Log identity to report. if empty, defaults to the name of argv[0] + # (usually "unbound"). + # log-identity: "" + + # print UTC timestamp in ascii to logfile, default is epoch in seconds. + log-time-ascii: no + + # print one line with time, IP, name, type, class for every query. + log-queries: no + + # print one line per reply, with time, IP, name, type, class, rcode, + # timetoresolve, fromcache and responsesize. + log-replies: no + + # log with tag 'query' and 'reply' instead of 'info' for + # filtering log-queries and log-replies from the log. + log-tag-queryreply: no + + # log the local-zone actions, like local-zone type inform is enabled + # also for the other local zone types. + log-local-actions: no + + # print log lines that say why queries return SERVFAIL to clients. + log-servfail: no + + # file to read root hints from. + # get one from https://www.internic.net/domain/named.cache + # or from dns-root-hints package in Alpine Linux + root-hints: "var/named.root" + + # enable to not answer id.server and hostname.bind queries. + hide-identity: yes + + # Set the identity to report. If set to "", the default, then the + # hostname of the server is returned. + identity: "DNS" + + # enable to not answer version.server and version.bind queries. + hide-version: yes + + # enable to not answer trustanchor.unbound queries. + hide-trustanchor: yes + + # if enabled the HTTP header User-Agent is not set. Use with + # caution as some webserver configurations may reject HTTP + # requests lacking this header. If needed, it is better to + # explicitly set the http-user-agent below. + hide-http-user-agent: yes + + # Set the HTTP User-Agent header for outgoing HTTP requests. If + # set to "", the default, then the package name and version are + # used. + http-user-agent: "DNS" + + # Harden against very small EDNS buffer sizes. + harden-short-bufsize: yes + + # Harden against unseemly large queries. + harden-large-queries: yes + + # Harden against out of zone rrsets, to avoid spoofing attempts. + harden-glue: yes + + # Harden against receiving dnssec-stripped data. If you turn it + # off, failing to validate dnskey data for a trustanchor will + # trigger insecure mode for that zone (like without a trustanchor). + # Default on, which insists on dnssec data for trust-anchored zones. + harden-dnssec-stripped: yes + + # Harden against queries that fall under dnssec-signed nxdomain names. + harden-below-nxdomain: yes + + # Harden the referral path by performing additional queries for + # infrastructure data. Validates the replies (if possible). + # Default off, because the lookups burden the server. Experimental + # implementation of draft-wijngaards-dnsext-resolver-side-mitigation. + harden-referral-path: no + + # Harden against algorithm downgrade when multiple algorithms are + # advertised in the DS record. If no, allows the weakest algorithm + # to validate the zone. + harden-algo-downgrade: yes + + # Sent minimum amount of information to upstream servers to enhance + # privacy. Only sent minimum required labels of the QNAME and set QTYPE + # to A when possible. + qname-minimisation: yes + + # QNAME minimisation in strict mode. Do not fall-back to sending full + # QNAME to potentially broken nameservers. A lot of domains will not be + # resolvable when this option in enabled. + # This option only has effect when qname-minimisation is enabled. + qname-minimisation-strict: no + + # Aggressive NSEC uses the DNSSEC NSEC chain to synthesize NXDOMAIN + # and other denials, using information from previous NXDOMAINs answers. + aggressive-nsec: yes + + # Use 0x20-encoded random bits in the query to foil spoof attempts. + # This feature is an experimental implementation of draft dns-0x20. + use-caps-for-id: no + + # Enforce privacy of these addresses. Strips them away from answers. + # It may cause DNSSEC validation to additionally mark it as bogus. + # Protects against 'DNS Rebinding' (uses browser as network proxy). + # Only 'private-domain' and 'local-data' names are allowed to have + # these private addresses. No default. + private-address: 10.0.0.0/8 + private-address: 172.16.0.0/12 + private-address: 192.168.0.0/16 + private-address: 169.254.0.0/16 + private-address: fd00::/8 + private-address: fe80::/10 + private-address: ::ffff:0:0/96 + + # If nonzero, unwanted replies are not only reported in statistics, + # but also a running total is kept per thread. If it reaches the + # threshold, a warning is printed and a defensive action is taken, + # the cache is cleared to flush potential poison out of it. + # A suggested value is 10000000, the default is 0 (turned off). + unwanted-reply-threshold: 10000000 + + # if yes, localhost is added to the do-not-query-address entries, + # both IP6 ::1 and IP4 127.0.0.1/8. If no, then localhost can be + # used to send queries to. Default is yes. + do-not-query-localhost: yes + + # if yes, message cache elements are prefetched before they expire + # to keep the cache up to date. Default is no. Turning it on + # gives about 10 percent more traffic and load on the machine, but + # popular items do not expire from the cache. + prefetch: yes + + # if yes, fetch the DNSKEYs earlier in the validation process, + # when a DS record is encountered. This lowers the latency of + # requests. It does use a little more CPU. Also if the cache is + # set to 0, it is no use. Default is no. + prefetch-key: yes + + # deny queries of type ANY with an empty response. + deny-any: yes + + # if yes, Unbound rotates RRSet order in response. + rrset-roundrobin: yes + + # if yes, Unbound doesn't insert authority/additional sections + # into response messages when those sections are not required. + minimal-responses: yes + + # File with trusted keys, kept uptodate using RFC5011 probes, + # initial file like trust-anchor-file, then it stores metadata. + # Use several entries, one per domain name, to track multiple zones. + # + # If you want to perform DNSSEC validation, run unbound-anchor before + # you start Unbound (i.e. in the system boot scripts). + # And then enable the auto-trust-anchor-file config item. + # Please note usage of unbound-anchor root anchor is at your own risk + # and under the terms of our LICENSE (see that file in the source). + auto-trust-anchor-file: "var/trusted-key.key" + + # Should additional section of secure message also be kept clean of + # unsecure data. Useful to shield the users of this validator from + # potential bogus data in the additional section. All unsigned data + # in the additional section is removed from secure messages. + val-clean-additional: yes + + # Serve expired responses from cache, with serve-expired-reply-ttl in + # the response, and then attempt to fetch the data afresh. + serve-expired: yes + # + # Limit serving of expired responses to configured seconds after + # expiration. 0 disables the limit. + serve-expired-ttl: 172800 + # + # Set the TTL of expired records to the serve-expired-ttl value after a + # failed attempt to retrieve the record from upstream. This makes sure + # that the expired records will be served as long as there are queries + # for it. + serve-expired-ttl-reset: yes + # + # TTL value to use when replying with expired data. + # serve-expired-reply-ttl: 30 + # + # Time in milliseconds before replying to the client with expired data. + # This essentially enables the serve-stale behavior as specified in + # RFC 8767 that first tries to resolve before + # immediately responding with expired data. 0 disables this behavior. + # A recommended value is 1800. + # serve-expired-client-timeout: 0 + + # Certificates used to authenticate connections made upstream. + tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt" + + # ratelimit for uncached, new queries, this limits recursion effort. + # ratelimiting is experimental, and may help against randomqueryflood. + # if 0(default) it is disabled, otherwise state qps allowed per zone. + ratelimit: 1000 + + +# Remote control config section. +remote-control: + # Enable remote control with unbound-control(8) here. + # set up the keys and certificates with unbound-control-setup. + control-enable: no + +# Forward zones +# Create entries like below, to make all queries for 'example.com' and +# 'example.org' go to the given list of servers. These servers have to handle +# recursion to other nameservers. List zero or more nameservers by hostname +# or by ipaddress. Use an entry with name "." to forward all queries. +# If you enable forward-first, it attempts without the forward if it fails. +# forward-zone: +# name: "example.com" +# forward-addr: 192.0.2.68 +# forward-addr: 192.0.2.73@5355 # forward to port 5355. +# forward-first: no +# forward-tcp-upstream: no +# forward-tls-upstream: no +# forward-no-cache: no +# forward-zone: +# name: "example.org" +# forward-host: fwd.example.com +forward-zone: + name: "." + forward-tls-upstream: yes + forward-no-cache: no +{% for item in unbound_upstream_nameservers %} + forward-addr: {{ item }} +{% endfor %} diff --git a/roles/system/templates/earlyoom/conf.j2 b/roles/system/templates/earlyoom/conf.j2 new file mode 100644 index 0000000..4034fee --- /dev/null +++ b/roles/system/templates/earlyoom/conf.j2 @@ -0,0 +1,29 @@ +# Configuration for /etc/init.d/earlyoom + +{% if earlyoom.mem_min_percent is defined %} +# Set available memory minimum to PERCENT of total (default 10 %). [-m] +mem_min_percent={{ earlyoom.mem_min_percent }} + +{% endif %} +{% if earlyoom.swap_min_percent is defined %} +# Set free swap minimum to PERCENT of total (default 10 %). [-s] +# Note: Both memory and swap must be below minimum for earlyoom to act. +swap_min_percent={{ earlyoom.swap_min_percent }} + +{% endif %} +{% if earlyoom.mem_min_size is defined %} +# Set available memory minimum to SIZE[,KILL_SIZE] KiB. [-M] +mem_min_size={{ earlyoom.mem_min_size }} + +{% endif %} +{% if earlyoom.swap_min_size is defined %} +# Set free swap minimum to SIZE[,KILL_SIZE] KiB. [-S] +swap_min_size={{ swap_min_size }} + +{% endif %} +{% if earlyoom.set_priority %} +# Set niceness of earlyoom to -20 and oom_score_adj to -100? yes/no [-p] +set_priority=yes + +{% endif%} +supervisor="supervise-daemon" diff --git a/roles/system/templates/essential/motd.j2 b/roles/system/templates/essential/motd.j2 new file mode 100644 index 0000000..501bb87 --- /dev/null +++ b/roles/system/templates/essential/motd.j2 @@ -0,0 +1,2 @@ +Welcome to Alpine, {{ username }}! + diff --git a/roles/system/templates/essential/repositories.j2 b/roles/system/templates/essential/repositories.j2 new file mode 100644 index 0000000..222ea48 --- /dev/null +++ b/roles/system/templates/essential/repositories.j2 @@ -0,0 +1,3 @@ +{{ repository }}/edge/main +{{ repository }}/edge/community +{{ repository }}/edge/testing diff --git a/roles/system/templates/networking/connman-service.config.j2 b/roles/system/templates/networking/connman-service.config.j2 new file mode 100644 index 0000000..5025e16 --- /dev/null +++ b/roles/system/templates/networking/connman-service.config.j2 @@ -0,0 +1,21 @@ +[global] +Name = Ethernet +Description = Configuration for Ethernet interfaces + +{% for interface in network_interfaces %} +[service_ethernet_{{ interface.name }}] +Type = ethernet +DeviceName = {{ interface.name }} +{% if interface.ip4_type == 'static' %} +IPv4 = {{ interface.ip4_addr | ansible.utils.ipv4('address') }}/{{ interface.ip4_addr | ansible.utils.ipv4('netmask') }}/{{ interface.ip4_gateway | ansible.utils.ipv4('address') }} +{% else %} +IPv4 = {{ interface.ip4_type }} +{% endif %} +{% if interface.ip6_type == 'static' %} +IPv6 = {{ interface.ip6_addr | ansible.utils.ipv6('host/prefix') }}/{{ interface.ip6_gateway | ansible.utils.ipv6('address') }} +{% else %} +IPv6 = {{ interface.ip6_type }} +{% endif %} +IPv6.Privacy = preferred + +{% endfor %} diff --git a/roles/system/templates/networking/interfaces.j2 b/roles/system/templates/networking/interfaces.j2 new file mode 100644 index 0000000..991b7d3 --- /dev/null +++ b/roles/system/templates/networking/interfaces.j2 @@ -0,0 +1,31 @@ +auto lo +iface lo inet loopback + +{% for interface in network_interfaces %} +auto {{ interface.name }} + +iface {{ interface.name }} inet {{ interface.ip4_type }} +{% if interface.ip4_type == 'dhcp' and interface.udhcpc_opts is string and dhcp_client == 'udhcpc' %} + udhcpc_opts {{ interface.udhcpc_opts }} +{% endif %} +{% if interface.ip4_type == 'static' %} + address {{ interface.ip4_addr | ansible.utils.ipv4('host/prefix') }} + gateway {{ interface.ip4_gateway | ansible.utils.ipv4('address') }} +{% endif %} + post-up /sbin/resolvconf -u + +{# +Use SLAAC for udhcpc and dhclient, DHCPv6 for dhcpcd when ip6_type == 'auto'. +See https://wiki.alpinelinux.org/wiki/Configure_Networking#IPv6_DHCP_Configuration. +#} +{% if interface.ip6_type == 'static' or dhcp_client != 'dhcpcd' %} +iface {{ interface.name }} inet6 {{ interface.ip6_type }} +{% if interface.ip6_type == 'static' %} + address {{ interface.ip6_addr | ansible.utils.ipv6('host/prefix') }} + gateway {{ interface.ip6_gateway | ansible.utils.ipv6('address') }} + pre-up echo 0 > /proc/sys/net/ipv6/conf/{{ interface.name }}/accept_ra +{% endif %} + post-up /sbin/resolvconf -u +{% endif %} + +{% endfor %} diff --git a/roles/system/templates/nftables/nftables.j2 b/roles/system/templates/nftables/nftables.j2 new file mode 100644 index 0000000..b2f4d82 --- /dev/null +++ b/roles/system/templates/nftables/nftables.j2 @@ -0,0 +1,178 @@ +{% set all_public_network_interfaces = ( ansible_interfaces | select('match', '^(eth|wlan|(en|wl|ww)[osxpP])[0-9]+') | list ) -%} +#!/usr/sbin/nft -f + +# NOTE: +# Dynamic blacklisting: +# - https://wiki.nftables.org/wiki-nftables/index.php/Meters +# - https://wiki.archlinux.org/title/Nftables#Dynamic_blackhole +# - https://unix.stackexchange.com/questions/581964/create-dynamic-blacklist-with-nftables +# +# Early broken packages dropping in netdev: +# - https://wiki.gentoo.org/wiki/Nftables#Family_netdev_and_ingress_hook +# - https://serverfault.com/questions/772195/drop-fragmented-packets-in-nftables +# +# udev's network interface naming scheme: +# - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/consistent-network-interface-device-naming_configuring-and-managing-networking +# - https://systemd.io/PREDICTABLE_INTERFACE_NAMES/ +# +# UDP is stateless, but the kernel can still mark a few conntrack states on the packets: +# - https://blog.cloudflare.com/everything-you-ever-wanted-to-know-about-udp-sockets-but-were-afraid-to-ask-part-1/ +# - https://www.rigacci.org/wiki/lib/exe/fetch.php/doc/appunti/linux/sa/iptables/conntrack.html +# - https://www.frozentux.net/iptables-tutorial/chunkyhtml/x1555.html +# - https://github.com/torvalds/linux/blob/master/net/ipv4/udp.c + +# Clear all prior state +flush ruleset + +table netdev filter { + chain ingress { + # Hook priority list: https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks + # The priority needs to be lower than -400 (NF_IP_PRI_CONNTRACK_DEFRAG) to see fragments + type filter hook ingress devices = { {{ all_public_network_interfaces | join(', ') }} } priority -500; policy accept; + + # Drop all fragments. + ip frag-off & 0x1fff != 0 drop + + # Drop XMAS packets. + tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg drop + + # Drop NULL packets. + tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 drop + + # Drop uncommon MSS values. + tcp flags syn tcp option maxseg size 1-535 drop + } +} + +# Basic IPv4/IPv6 stateful firewall for server/workstation. +table inet filter { + set blackhole { type ipv4_addr; flags dynamic, timeout; size 65535; } + set blackhole6 { type ipv6_addr; flags dynamic, timeout; size 65535; } + + # NOTE: connlimit shouldn't be used with timeout flag (same goes for "update" set statement) + set connlimit { type ipv4_addr; flags dynamic; size 65535; } + set connlimit6 { type ipv6_addr; flags dynamic; size 65535; } + + chain input { + type filter hook input priority 0; policy drop; + + iif lo accept \ + comment "Accept any localhost traffic" + + ct state { established, related } accept \ + comment "Accept traffic originated from us" + + ct state invalid drop \ + comment "Drop invalid connections" + + tcp flags & (fin|syn|rst|ack) != syn ct state new drop \ + comment "Drop non-SYN packets" + + tcp dport 113 reject with icmpx type port-unreachable \ + comment "Reject AUTH to make it fail fast" + + iif != lo ip daddr 127.0.0.1/8 drop \ + comment "Block spoofing as localhost (IPv4)" + iif != lo ip6 daddr ::1/128 drop \ + comment "Block spoofing as localhost (IPv6)" + + udp dport mdns ip daddr 224.0.0.251 accept \ + comment "Accept mDNS" + udp dport mdns ip6 daddr ff02::fb accept \ + comment "Accept mDNS" + + jump input_dhcp_client + jump input_icmp + + # Blacklisting should be done before stateful accept rules + ip saddr @blackhole counter drop + ip6 saddr @blackhole6 counter drop + + # Drop future attempts on opened ports if there are already 3 established connections +{% if opened_ports.tcp is sequence and opened_ports.tcp | length > 0 %} + tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \ + add @connlimit { ip saddr ct count over 3 } drop + tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \ + add @connlimit6 { ip6 saddr ct count over 3 } drop +{% endif %} +{% if opened_ports.udp is sequence and opened_ports.udp | length > 0 %} + udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \ + add @connlimit { ip saddr ct count over 3 } drop + udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \ + add @connlimit6 { ip6 saddr ct count over 3 } drop +{% endif %} + + # Allow opened ports but also dynamically add them to the blacklist +{% if opened_ports.tcp is sequence and opened_ports.tcp | length > 0 %} + tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \ + add @blackhole { ip saddr timeout 60s limit rate 10/second } accept + tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \ + add @blackhole6 { ip6 saddr timeout 60s limit rate 10/second } accept +{% endif %} +{% if opened_ports.udp is sequence and opened_ports.udp | length > 0 %} + udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \ + add @blackhole { ip saddr timeout 60s limit rate 10/second } accept + udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \ + add @blackhole6 { ip6 saddr timeout 60s limit rate 10/second } accept +{% endif %} + } + + chain forward { + type filter hook forward priority 0; policy drop; + } + + chain output { + type filter hook output priority 0; policy accept; + } + + chain input_dhcp_client { + udp sport 67 udp dport 68 accept \ + comment "Accept DHCP client input traffic" + + ip6 saddr fe80::/10 udp sport 547 udp dport 546 accept \ + comment "Accept DHCPv6 replies from IPv6 link-local addresses" + } + + chain input_icmp { + ip protocol igmp accept \ + comment "Accept IGMP" + + ip protocol icmp icmp type { + echo-reply, # type 0 + destination-unreachable, # type 3 + echo-request, # type 8 + time-exceeded, # type 11 + parameter-problem, # type 12 + } limit rate 10/second burst 4 packets accept \ + comment "Accept ICMP" + + icmpv6 type { + destination-unreachable, # type 1 + packet-too-big, # type 2 + time-exceeded, # type 3 + parameter-problem, # type 4 + echo-request, # type 128 + echo-reply, # type 129 + } limit rate 10/second burst 4 packets accept \ + comment "Accept basic IPv6 functionality" + + icmpv6 type { + nd-router-solicit, # type 133 + nd-router-advert, # type 134 + nd-neighbor-solicit, # type 135 + nd-neighbor-advert, # type 136 + } ip6 hoplimit 255 accept \ + comment "Allow IPv6 SLAAC" + + icmpv6 type { + mld-listener-query, # type 130 + mld-listener-report, # type 131 + mld-listener-reduction, # type 132 + mld2-listener-report, # type 143 + } ip6 saddr fe80::/10 accept \ + comment "Allow IPv6 multicast listener discovery on link-local" + } +} + +# The state of stateful objects saved on the nftables service stop. +include "/var/lib/nftables/*.nft" diff --git a/roles/system/templates/ntpd/chrony.conf.j2 b/roles/system/templates/ntpd/chrony.conf.j2 new file mode 100644 index 0000000..bc0483c --- /dev/null +++ b/roles/system/templates/ntpd/chrony.conf.j2 @@ -0,0 +1,31 @@ +# /etc/chrony/chrony.conf +# See chrony.conf(5) + +{% for pool in ntp_opts.pools %} +pool {{ pool }} iburst maxsources 5 +{% endfor %} + +{% for server in ntp_opts.servers %} +server {{ server }} iburst {%- if ntp_opts.nts_enabled | bool %} nts +{% endif %} +{% endfor %} + +# 3 are always more trustworthy than 1 +minsources 3 + +# Disable all non-NTS sources if at least 1 source has NTS enabled +authselectmode prefer + +# Cache NTS keys and cookies, so NTS-KE session doesn't need to be repeated on each start +ntsdumpdir /var/lib/chrony + +driftfile /var/lib/chrony/chrony.drift + +# Jump off at boot +makestep 1.0 3 + +# Occationally copy system time to RTC +rtcsync + +# Don't open any ports for monitoring +cmdport 0 diff --git a/roles/system/templates/ntpd/ntp.conf.j2 b/roles/system/templates/ntpd/ntp.conf.j2 new file mode 100644 index 0000000..4b2aaa0 --- /dev/null +++ b/roles/system/templates/ntpd/ntp.conf.j2 @@ -0,0 +1,58 @@ +# /etc/ntp.conf +# Ref: +# - ntp.conf(5) +# - https://gitlab.com/NTPsec/ntpsec/-/tree/master/etc/ntp.d + +# There is no peer in ntpsec, just pool and server +{% for pool in ntp_opts.pools %} +pool {{ pool }} iburst +{% endfor %} + +{% for server in ntp_opts.servers %} +server {{ server }} iburst {%- if ntp_opts.nts_enabled | bool %} nts +{% endif %} +{% endfor %} + +# From ntpd/ntpd.c +# /* +# * ... +# * +# * With 2 working servers: +# * if they don't agree, you can't tell which one is correct +# * With 3 working servers, 2 can outvote a falseticker +# * With 4 servers, you still have 3 if one is down. +# */ +tos minsane 4 + +# minclock should be greater than minsane +tos minclock 5 + +# minclock + 2 (or 3) + number of pools +# Also should be an odd number +{% set maxclock = 7 + (ntp_opts.pools | length) %} +tos maxclock {{ (maxclock | int %2 == 0) | ternary(maxclock + 1, maxclock) | int }} + +# Exchange time with everybody, but don't allow configuration. +# This is the right security setup for 99% of deployments. +restrict default kod limited nomodify noquery +restrict -6 default kod limited nomodify noquery + +# Local users may interrogate the NTP server more closely. +restrict 127.0.0.1 +restrict -6 ::1 + +driftfile /var/lib/ntp/ntp.drift + +# Where to store keys to encrypt NTS cookies +nts cookie /var/lib/ntp/nts-keys + +# Where to log NTP statistics +# NOTE: /var/lib/ntp and /var/log/ntpstats are already created by ntpsec package with correct permissions +statsdir /var/log/ntpstats/ + +statistics loopstats peerstats clockstats +filegen loopstats file loopstats type day enable +filegen peerstats file peerstats type day enable +filegen clockstats file clockstats type day enable + +logconfig =syncall +clockall +peerall +sysall diff --git a/roles/system/templates/ntpd/ntpd.conf.j2 b/roles/system/templates/ntpd/ntpd.conf.j2 new file mode 100644 index 0000000..578d1da --- /dev/null +++ b/roles/system/templates/ntpd/ntpd.conf.j2 @@ -0,0 +1,17 @@ +# /etc/ntpd.conf +# See ntpd.conf(5) and /etc/examples/ntpd.conf + +{% for pool in ntp_opts.pools %} +servers {{ pool }} +{% endfor %} + +{% for server in ntp_opts.servers %} +server {{ server }} +{% endfor %} + +sensor * + +constraint from "9.9.9.9" "2620:fe::fe" +constraint from "1.1.1.1" "2606:4700:4700::1111" + +constraints from "fosstodon.org" diff --git a/roles/system/templates/snapshot/btrbk.conf.j2 b/roles/system/templates/snapshot/btrbk.conf.j2 new file mode 100644 index 0000000..d8f7561 --- /dev/null +++ b/roles/system/templates/snapshot/btrbk.conf.j2 @@ -0,0 +1,66 @@ +# btrbk.conf +# See btrbk.conf(5) for more details. + +{% if btrbk.options.logfile is defined %} +transaction_log {{ btrbk.options.logfile }} +{% endif %} + +{% if btrbk.options.syslog is defined %} +transaction_syslog {{ btrbk.options.syslog }} +{% endif %} + +{% if btrbk.options.lockfile is defined %} +lockfile {{ btrbk.options.lockfile }} +{% endif %} + +{% if btrbk.options.ssh.remote_user is defined %} +ssh_user {{ btrbk.options.ssh.remote_user }} +{% endif %} +{% if btrbk.options.ssh.private_key_path is defined %} +ssh_identity {{ btrbk.options.ssh.private_key_path }} +{% endif %} + +{% for type in ['snapshot', 'target', 'archive'] %} + +{% if btrbk.options[type].policy is defined %} +{{ type }}_preserve {{ btrbk.options[type].policy }} +{% endif %} +{% if btrbk.options[type].min_age is defined %} +{{ type }}_preserve_min {{ btrbk.options[type].min_age }} +{% endif %} +{% endfor %} + +timestamp_format {{ btrbk.options.timestamp_format }} + +compat busybox + +backend_local_user btrfs-progs-doas +backend_remote btrfs-progs-doas + +stream_compress zstd + +{% if btrbk.options.stream_buffer.local is defined %} +stream_buffer {{ btrbk.options.stream_buffer.local }} +{% endif %} +{% if btrbk.options.stream_buffer.remote is defined %} +stream_buffer_remote {{ btrbk.options.stream_buffer.remote }} +{% endif %} + +{% for volume in btrbk.volumes %} + +volume {{ volume.path }} + snapshot_dir {{ volume.snapshot_dir }} +{% if volume.archive_exclude_pattern is defined %} + archive_exclude {{ archive_exclude_pattern }} +{% endif %} +{% if volume.subvolumes is defined %} +{% for subvolume in volume.subvolumes %} + subvolume {{ subvolume }} +{% endfor %} +{% endif %} +{% if volume.targets is defined %} +{% for target in volume.targets %} + target {{ target }} +{% endfor %} +{% endif %} +{% endfor %} diff --git a/roles/system/templates/snapshot/sanoid.conf.j2 b/roles/system/templates/snapshot/sanoid.conf.j2 new file mode 100644 index 0000000..79f5ad4 --- /dev/null +++ b/roles/system/templates/snapshot/sanoid.conf.j2 @@ -0,0 +1,18 @@ +{% for dataset in (sanoid.datasets | dict2items) %} +[{{ dataset.key }}] +{% for option in (dataset.value | dict2items) %} + {{ option.key }} = {{ option.value }} +{% endfor %} + +{% endfor %} +############################# +# templates below this line # +############################# + +{% for template in (sanoid.templates | dict2items) %} +[template_{{ template.key }}] +{% for option in (template.value | dict2items) %} + {{ option.key }} = {{ option.value }} +{% endfor %} + +{% endfor %} diff --git a/roles/system/templates/snapshot/snapper.j2 b/roles/system/templates/snapshot/snapper.j2 new file mode 100644 index 0000000..04702c7 --- /dev/null +++ b/roles/system/templates/snapshot/snapper.j2 @@ -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) }}" diff --git a/roles/system/templates/tlp/00-custom.j2 b/roles/system/templates/tlp/00-custom.j2 new file mode 100644 index 0000000..63d2c18 --- /dev/null +++ b/roles/system/templates/tlp/00-custom.j2 @@ -0,0 +1 @@ +{% include 'by_host/' + ansible_hostname + '/config.j2' ignore missing %} diff --git a/roles/system/templates/tlp/by_host/alpine-tp/config.j2 b/roles/system/templates/tlp/by_host/alpine-tp/config.j2 new file mode 100644 index 0000000..eef4418 --- /dev/null +++ b/roles/system/templates/tlp/by_host/alpine-tp/config.j2 @@ -0,0 +1,20 @@ +TLP_DEFAULT_MODE=BAT + +CPU_SCALING_MIN_FREQ_ON_BAT=1400000 +CPU_SCALING_MAX_FREQ_ON_BAT=1700000 +CPU_SCALING_MIN_FREQ_ON_AC=1400000 +CPU_SCALING_MAX_FREQ_ON_AC=2100000 + +DISK_SPINDOWN_TIMEOUT_ON_AC="keep keep" +DISK_SPINDOWN_TIMEOUT_ON_BAT="keep 1" +DISK_IOSCHED="mq-deadline mq-deadline" + +PCIE_ASPM_ON_BAT=default +PCIE_ASPM_ON_AC=default + +DEVICES_TO_DISABLE_ON_STARTUP="bluetooth wifi wwan" +DEVICES_TO_DISABLE_ON_BAT_NOT_IN_USE="bluetooth wifi wwan" + +START_CHARGE_THRESH_BAT0=75 +STOP_CHARGE_THRESH_BAT0=90 +RESTORE_THRESHOLDS_ON_BAT=1 diff --git a/roles/system/templates/zram/zram-start.j2 b/roles/system/templates/zram/zram-start.j2 new file mode 100644 index 0000000..5dda10c --- /dev/null +++ b/roles/system/templates/zram/zram-start.j2 @@ -0,0 +1,18 @@ +#!/bin/sh + +# ensure zram module is loaded +/sbin/modprobe zram +{% for i in range(zram_num_devices) %} + +## SETUP FOR ZRAM{{ i }} +# set compression algorithm +echo zstd > /sys/block/zram{{ i }}/comp_algorithm +# set disksize +echo {{ ansible_memtotal_mb }}M > /sys/block/zram{{ i }}/disksize +# set memory limit +echo {{ ansible_memtotal_mb }}M > /sys/block/zram{{ i }}/mem_limit +# activate swaps +/sbin/mkswap -L zram{{ i }} /dev/zram{{ i }} +# swapon +/sbin/swapon -p 10 /dev/zram{{ i }} +{% endfor %} diff --git a/roles/system/templates/zram/zram-stop.j2 b/roles/system/templates/zram/zram-stop.j2 new file mode 100644 index 0000000..98f1f5f --- /dev/null +++ b/roles/system/templates/zram/zram-stop.j2 @@ -0,0 +1,9 @@ +#!/bin/sh +{% for i in range(zram_num_devices) %} + +## TURN OFF DEVICE ZRAM{{ i }} +/sbin/swapoff /dev/zram{{ i }} +echo 1 > /sys/block/zram{{ i }}/reset +{% endfor %} + +/sbin/modprobe -r zram diff --git a/roles/system/templates/zram/zram.j2 b/roles/system/templates/zram/zram.j2 new file mode 100644 index 0000000..c46fb4e --- /dev/null +++ b/roles/system/templates/zram/zram.j2 @@ -0,0 +1 @@ +options zram num_devices={{ zram_num_devices }} diff --git a/roles/theme/tasks/fonts/bobbers.yml b/roles/theme/tasks/fonts/bobbers.yml index 8e2614b..02ad034 100644 --- a/roles/theme/tasks/fonts/bobbers.yml +++ b/roles/theme/tasks/fonts/bobbers.yml @@ -5,7 +5,7 @@ dest: /tmp/Bobbers.zip mode: '644' -- name: fonts | Decompress downloaded Bobbers font archive +- name: fonts | Decompress downloaded Bobbers font archive # noqa: command-instead-of-module command: unzip /tmp/Bobbers.zip 'Bobbers Personal Use.ttf' -d {{ xdg_dir.data_home }}/fonts/ args: creates: '{{ xdg_dir.data_home }}/fonts/Bobbers Personal Use.ttf' diff --git a/roles/theme/tasks/fonts/iosevka.yml b/roles/theme/tasks/fonts/iosevka.yml index 0aab24e..2796f49 100644 --- a/roles/theme/tasks/fonts/iosevka.yml +++ b/roles/theme/tasks/fonts/iosevka.yml @@ -18,7 +18,7 @@ - iosevka-etoile - iosevka-slab -- name: fonts | Decompress downloaded Iosevka font archives +- name: fonts | Decompress downloaded Iosevka font archives # noqa: command-instead-of-module command: unzip /tmp/{{ item }}.zip -d {{ xdg_dir.data_home }}/fonts/iosevka args: creates: '{{ xdg_dir.data_home }}/fonts/iosevka/{{ item }}.ttc' diff --git a/roles/theme/tasks/sound.yml b/roles/theme/tasks/sound.yml index cdfd23e..b7fa947 100644 --- a/roles/theme/tasks/sound.yml +++ b/roles/theme/tasks/sound.yml @@ -49,7 +49,7 @@ mode: '644' checksum: sha256:bc8f9398d653dfe84a6561ed783ab1e4b53c03c2652f747cf9c32412292fb307 -- name: soundtheme | Install sakura and kayo sound themes +- name: soundtheme | Install sakura and kayo sound themes # noqa: command-instead-of-module command: 'tar -xzf /tmp/{{ item }}-thema.tar.gz' args: chdir: '{{ xdg_dir.data_home }}/sounds' @@ -58,7 +58,7 @@ - kayo - sakura -- name: soundtheme | Extract Libreoffice sound themes to /tmp +- name: soundtheme | Extract Libreoffice sound themes to /tmp # noqa: command-instead-of-module command: 'tar -xzf /tmp/lbr_ubuntu_{{ item }}.tgz' args: chdir: /tmp diff --git a/themes/nord.yml b/themes/nord.yml index 218888b..8ac416c 100644 --- a/themes/nord.yml +++ b/themes/nord.yml @@ -1,5 +1,4 @@ --- -# Colors for theming foreground: '#d8dee9' background: '#2e3440' grey1: '#3b4252' @@ -18,21 +17,3 @@ yellow: '#ebcb8b' green: '#a3be8c' purple: '#b48ead' highlight: '#7b88a1' - -# Terminal 16 colors -color0: '#3b4252' -color1: '#bf616a' -color2: '#a3be8c' -color3: '#ebcb8b' -color4: '#81a1c1' -color5: '#b48ead' -color6: '#88c0d0' -color7: '#e5e9f0' -color8: '#4c566a' -color9: '#bf616a' -color10: '#a3be8c' -color11: '#ebcb8b' -color12: '#81a1c1' -color13: '#b48ead' -color14: '#8fbcbb' -color15: '#eceff4' diff --git a/themes/onedark.yml b/themes/onedark.yml index f068fb5..c415dc1 100644 --- a/themes/onedark.yml +++ b/themes/onedark.yml @@ -1,43 +1,23 @@ --- -# Colors for theming foreground: '#abb2bf' background: '#282c34' grey1: '#3e4452' -grey2: '#4b5263' -grey3: '#5c6470' -grey_bright: '#73797e' -white1: '#bbc2cf' -white2: '#dfdfdf' -teal: '#5699af' +grey2: '#4b5362' +grey3: '#5c6370' +grey_bright: '#7f848e' +white1: '#abb2bf' +white2: '#ffffff' +teal: '#2bbac5' cyan: '#56b6c2' blue: '#61afef' -dark_blue: '#2257a0' red: '#e06c75' dark_red: '#be5046' orange: '#d19a66' yellow: '#e5c07b' green: '#98c379' purple: '#c678dd' -highlight: '#9ca0a4' +highlight: '#9da5b4' # Not in OneDark palette but looks nice # https://github.com/TheCactusBlue/dotfiles/blob/master/dotfiles/config/polybar/config.ini violet: '#8c9cff' - -# Terminal 16 colors -color0: '#3e4452' -color1: '#be5046' -color2: '#98c379' -color3: '#e5c07b' -color4: '#61afef' -color5: '#c678dd' -color6: '#56b6c2' -color7: '#abb2bf' -color8: '#4b5263' -color9: '#e06c75' -color10: '#98c379' -color11: '#e5c07b' -color12: '#61afef' -color13: '#c678dd' -color14: '#56b6c2' -color15: '#bbc2cf'