SHA256
1
0

Compare commits

..

12 Commits

Author SHA256 Message Date
6aefc82163 Migrate to new apps role 2026-02-02 01:33:38 -05:00
10e0d7ad9c Simplify var names in apps role 2026-01-30 01:09:52 -05:00
6d38e1dc5c Refactor to reduce config in group_vars 2026-01-29 23:34:14 -05:00
1b0c289b9b Fix path references 2026-01-29 03:01:19 -05:00
44261c95fb Make roles more reusable 2026-01-29 02:49:52 -05:00
4436599693 Disable line-length linter 2026-01-29 01:06:35 -05:00
b5e082e400 Add scripts to role 2026-01-29 01:06:09 -05:00
62c413d42d Restructure files into roles 2026-01-29 00:41:10 -05:00
d868ab2c3c Refactor dotfiles role 2026-01-27 01:59:47 -05:00
77ad977cd3 filesystems: add new downloads device 2026-01-27 01:59:15 -05:00
23190684ea Add more dotfiles and templates 2026-01-27 01:56:55 -05:00
0cb596f4db Update playbook infra 2026-01-27 01:55:54 -05:00
88 changed files with 1968 additions and 1184 deletions

2
.ansible-lint Normal file
View File

@@ -0,0 +1,2 @@
skip_list:
- yaml[line-length]

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.ansible .ansible/
testing/ testing/
vault.yml

0
README.md Normal file
View File

View File

@@ -1,2 +1,3 @@
[defaults] [defaults]
inventory = inventories/hosts.ini inventory = inventories/hosts.ini
collections_paths = ./collections

View File

@@ -2,6 +2,7 @@
# Bootstrap Ansible environment # Bootstrap Ansible environment
# Usage: ./bootstrap [--force] # Usage: ./bootstrap [--force]
pip install --upgrade pip "$@" python -m ensurepip --upgrade
pip install --upgrade --requirement pip-requirements "$@" pip install --upgrade pip
ansible-galaxy install --role-file galaxy-requirements.yml "$@" pip install --upgrade -r requirements "$@"
ansible-galaxy install --role-file collections/requirements.yml "$@"

View File

@@ -2,4 +2,4 @@
# ansible-playbook playbook.yml -l testing-remote --ask-vault-pass --ask-become-pass --check --diff # ansible-playbook playbook.yml -l testing-remote --ask-vault-pass --ask-become-pass --check --diff
ansible-playbook playbook.yml -l testing-remote --ask-vault-pass --ask-become-pass --diff "$@" ansible-playbook playbook.yml -l workstation-remote-testing --ask-vault-pass --ask-become-pass --diff "$@"

View File

@@ -5,7 +5,7 @@
} }
], ],
"settings": { "settings": {
"ansible.python.interpreterPath": "/home/bryan/.virtualenvs/deploy/bin/python", "ansible.python.interpreterPath": "/home/bryan/develop/deploy/.venv/bin/python",
"search.followSymlinks": false, "search.followSymlinks": false,
}, },

View File

@@ -1,7 +0,0 @@
# see https://github.com/sigoden/aichat/blob/main/config.example.yaml
model: claude:claude-haiku-4-5-20251001
clients:
- type: claude
api_key: {{ ANTHROPIC_API_KEY }}

View File

@@ -1,8 +0,0 @@
# see `man dnf.conf` for defaults and possible options
[main]
# installonly_limit=3
# best=False
skip_if_unavailable=True
deltarpm=True
fastestmirror=True

View File

@@ -1,154 +0,0 @@
## Path: System/File systems/btrfs
## Type: string(none,stdout,journal,syslog)
## Default: "stdout"
#
# Output target for messages. Journal and syslog messages are tagged by the task name like
# 'btrfs-scrub' etc.
BTRFS_LOG_OUTPUT="journal"
## Path: System/File systems/btrfs
## Type: string
## Default: ""
#
# Run periodic defrag on selected paths. The files from a given path do not
# cross mount points or other subvolumes/snapshots. If you want to defragment
# nested subvolumes, all have to be listed in this variable.
# (Colon separated paths)
BTRFS_DEFRAG_PATHS=""
## Path: System/File systems/btrfs
## Type: string(none,daily,weekly,monthly)
## Default: "none"
## ServiceRestart: btrfsmaintenance-refresh
#
# Frequency of defrag.
BTRFS_DEFRAG_PERIOD="none"
## Path: System/File systems/btrfs
## Type: string
## Default: "+1M"
#
# Minimal file size to consider for defragmentation
BTRFS_DEFRAG_MIN_SIZE="+1M"
## Path: System/File systems/btrfs
## Type: string
## Default: "/"
#
# Which mountpoints/filesystems to balance periodically. This may reclaim unused
# portions of the filesystem and make the rest more compact.
# (Colon separated paths)
# The special word/mountpoint "auto" will evaluate all mounted btrfs
# filesystems
BTRFS_BALANCE_MOUNTPOINTS="auto"
## Path: System/File systems/btrfs
## Type: string(none,daily,weekly,monthly)
## Default: "weekly"
## ServiceRestart: btrfsmaintenance-refresh
#
# Frequency of periodic balance.
#
# The frequency may be specified using one of the listed values or
# in the format documented in the "Calendar Events" section of systemd.time(7),
# if available.
BTRFS_BALANCE_PERIOD="weekly"
## Path: System/File systems/btrfs
## Type: string
## Default: "5 10"
#
# The usage percent for balancing data block groups.
#
# Note: default values should not disturb normal work but may not reclaim
# enough block groups. If you observe that, add higher values but beware that
# this will increase IO load on the system.
BTRFS_BALANCE_DUSAGE="0 20 50 80"
## Path: System/File systems/btrfs
## Type: string
## Default: "5"
#
# The usage percent for balancing metadata block groups. The values are also
# used in case the filesystem has mixed blockgroups.
#
# Note: default values should not disturb normal work but may not reclaim
# enough block groups. If you observe that, add higher values but beware that
# this will increase IO load on the system.
BTRFS_BALANCE_MUSAGE="80"
## Path: System/File systems/btrfs
## Type: string
## Default: "/"
#
# Which mountpoints/filesystems to scrub periodically.
# (Colon separated paths)
# The special word/mountpoint "auto" will evaluate all mounted btrfs
# filesystems
BTRFS_SCRUB_MOUNTPOINTS="auto"
## Path: System/File systems/btrfs
## Type: string(none,weekly,monthly)
## Default: "monthly"
## ServiceRestart: btrfsmaintenance-refresh
#
# Frequency of periodic scrub.
#
# The frequency may be specified using one of the listed values or
# in the format documented in the "Calendar Events" section of systemd.time(7),
# if available.
BTRFS_SCRUB_PERIOD="quarterly"
## Path: System/File systems/btrfs
## Type: string(idle,normal)
## Default: "idle"
#
# Priority of IO at which the scrub process will run. Idle should not degrade
# performance but may take longer to finish.
BTRFS_SCRUB_PRIORITY="idle"
## Path: System/File systems/btrfs
## Type: boolean
## Default: "false"
#
# Do read-only scrub and don't try to repair anything.
BTRFS_SCRUB_READ_ONLY="false"
## Path: System/File systems/btrfs
## Description: Configuration for periodic fstrim
## Type: string(none,daily,weekly,monthly)
## Default: "none"
## ServiceRestart: btrfsmaintenance-refresh
#
# Frequency of periodic trim. Off by default so it does not collide with
# fstrim.timer . If you do not use the timer, turn it on here. The recommended
# period is 'weekly'.
#
# The frequency may be specified using one of the listed values or
# in the format documented in the "Calendar Events" section of systemd.time(7),
# if available.
BTRFS_TRIM_PERIOD="weekly"
## Path: System/File systems/btrfs
## Description: Configuration for periodic fstrim - mountpoints
## Type: string
## Default: "/"
#
# Which mountpoints/filesystems to trim periodically.
# (Colon separated paths)
# The special word/mountpoint "auto" will evaluate all mounted btrfs
# filesystems
BTRFS_TRIM_MOUNTPOINTS="auto"
## Path: System/File systems/btrfs
## Description: Configuration to allow concurrent jobs
## Type: boolean
## Default: "false"
#
# These maintenance tasks may compete for resources with each other, blocking
# out other tasks from using the file systems. This option will force
# these jobs to run in FIFO order when scheduled at overlapping times. This
# may include tasks scheduled to run when a system resumes or boots when
# the timer for these tasks(s) elapsed while the system was suspended
# or powered off.
BTRFS_ALLOW_CONCURRENCY="false"

View File

@@ -1,176 +0,0 @@
#
# Example btrbk configuration file
#
#
# Please refer to the btrbk.conf(5) man-page for a complete
# description of all configuration options.
#
# Note that the options can be overridden per volume/subvolume/target
# in the corresponding sections.
#
# Enable transaction log
transaction_log /var/log/btrbk.log
# Enable stream buffer. Adding a buffer between the sending and
# receiving side is generally a good idea.
# NOTE: If enabled, make sure the "mbuffer" package is installed on
# the target host!
#stream_buffer 512m
# Directory in which the btrfs snapshots are created. Relative to
# <volume-directory> of the volume section.
# If not set, the snapshots are created in <volume-directory>.
#
# If you want to set a custom name for the snapshot (and backups),
# use the "snapshot_name" option within the subvolume section.
#
# NOTE: btrbk does not autmatically create this directory, and the
# snapshot creation will fail if it is not present.
#
snapshot_dir .snapshots
# Always create snapshots. Set this to "ondemand" to only create
# snapshots if the target volume is reachable. Set this to "no" if
# snapshot creation is done by another instance of btrbk.
snapshot_create ondemand
# Perform incremental backups (set to "strict" if you want to prevent
# creation of non-incremental backups if no parent is found).
#incremental yes
# Specify after what time (in full hours after midnight) backups/
# snapshots are considered as a daily backup/snapshot
#preserve_hour_of_day 0
# Specify on which day of week weekly/monthly backups are to be
# preserved.
#preserve_day_of_week sunday
# Preserve all snapshots for a minimum period of time.
#snapshot_preserve_min 1d
# Retention policy for the source snapshots.
#snapshot_preserve <NN>h <NN>d <NN>w <NN>m <NN>y
# Preserve all backup targets for a minimum period of time.
#target_preserve_min no
# Retention policy for backup targets:
#target_preserve <NN>h <NN>d <NN>w <NN>m <NN>y
# Retention policy for archives ("btrbk archive" command):
#archive_preserve_min no
#archive_preserve <NN>h <NN>d <NN>w <NN>m <NN>y
# Specify SSH private key for "ssh://" volumes / targets:
#ssh_identity /etc/btrbk/ssh/id_ed25519
ssh_identity /root/.ssh/id_ed25519
ssh_user root
ssh_compression no
#ssh_cipher_spec default
compat_remote busybox
send_protocol 2
# Enable compression for remote btrfs send/receive operations:
stream_compress no
#stream_compress_level default
#stream_compress_threads default
# Enable lock file support: Ensures that only one instance of btrbk
# can be run at a time.
lockfile /var/lock/btrbk.lock
# Don't wait for transaction commit on deletion. Set this to "after"
# or "each" to make sure the deletion of subvolumes is committed to
# disk when btrbk terminates.
#btrfs_commit_delete no
#
# Volume section: "volume <volume-directory>"
#
# <volume-directory> Directory of a btrfs volume (or subvolume)
# containing the subvolume to be backuped
# (usually the mount-point of a btrfs filesystem
# mounted with subvolid=5 option)
#
# Subvolume section: "subvolume <subvolume-name>"
#
# <subvolume-name> Subvolume to be backuped, relative to
# <volume-directory> in volume section.
#
# Target section: "target <type> <volume-directory>"
#
# <type> (optional) type, defaults to "send-receive".
# <volume-directory> Directory of a btrfs volume (or subvolume)
# receiving the backups.
#
# NOTE: The parser does not care about indentation, this is only for
# human readability. The options always apply to the last section
# encountered, overriding the corresponding option of the upper
# section. This means that the global options must be set before any
# "volume" section.
#
#
# Example configuration:
#
# Backup to external disk mounted on /mnt/btr_backup
#volume /mnt/btr_pool
# no action if external disk is not attached
# snapshot_create ondemand
# propagates to all subvolume sections:
# target /mnt/btr_backup/_btrbk
# subvolume root_gentoo
# subvolume kvm
# use different retention policy for kvm backups
# target_preserve 7d 4w
# Backup to external disk as well as some remote host
#volume /mnt/btr_data
# subvolume home
# always create snapshot, even if targets are unreachable
# snapshot_create always
# target /mnt/btr_backup/_btrbk
# target ssh://backup.my-remote-host.com/mnt/btr_backup
# Backup from remote host, with different naming
#volume ssh://my-remote-host.com/mnt/btr_pool
# subvolume data_0
# snapshot_dir snapshots/btrbk
# snapshot_name data_main
# target /mnt/btr_backup/_btrbk/my-remote-host.com
# Resume backups from remote host which runs its own btrbk instance
# creating snapshots for "home" in "/mnt/btr_pool/btrbk_snapshots".
#volume ssh://my-remote-host.com/mnt/btr_pool
# snapshot_dir btrbk_snapshots
# snapshot_create no
# snapshot_preserve_min all
# subvolume home
# target /mnt/btr_backup/_btrbk/my-remote-host.com
snapshot_preserve_min 2d
snapshot_preserve 14d
target_preserve_min no
target_preserve 14d 10w *m
archive_preserve_min latest
archive_preserve 12m 10y
subvolume /
target_preserve 14d 10w 6m
snapshot_dir /.snapshots
target ssh://workstation/mnt/backup/laptop/root
# target ssh://router.lan/mnt/backup/laptop/root
volume /home
subvolume bryan
# target /mnt/backup/laptop/home
target ssh://workstation/mnt/backup/laptop/home
# target ssh://router.lan/mnt/backup/laptop/home

View File

@@ -1,5 +0,0 @@
[ids]
0001:0001:09b4e68d
[main]
leftmeta+leftshift+f23 = rightcontrol

View File

@@ -1,253 +0,0 @@
#
# Example btrbk configuration file
#
#
# Please refer to the btrbk.conf(5) man-page for a complete
# description of all configuration options.
# For more examples, see README.md included with this package.
#
# btrbk.conf(5): <https://digint.ch/btrbk/doc/btrbk.conf.5.html>
# README.md: <https://digint.ch/btrbk/doc/readme.html>
#
# Note that the options can be overridden per volume/subvolume/target
# in the corresponding sections.
#
# Enable transaction log
transaction_log /var/log/btrbk.log
# Specify SSH private key for remote connections
ssh_identity /home/bryan/.config/btrbk/id_ed25519
ssh_user root
# Use sudo if btrbk or lsbtr is run by regular user
backend_local_user btrfs-progs-sudo
# Enable stream buffer. Adding a buffer between the sending and
# receiving side is generally a good idea.
# NOTE: If enabled, make sure to install the "mbuffer" package!
stream_buffer 1g
# Directory in which the btrfs snapshots are created. Relative to
# <volume-directory> of the volume section.
# If not set, the snapshots are created in <volume-directory>.
#
# If you want to set a custom name for the snapshot (and backups),
# use the "snapshot_name" option within the subvolume section.
#
# NOTE: btrbk does not automatically create this directory, and the
# snapshot creation will fail if it is not present.
#
snapshot_dir .snapshots
# Always create snapshots. Set this to "ondemand" to only create
# snapshots if the target volume is reachable. Set this to "no" if
# snapshot creation is done by another instance of btrbk.
snapshot_create onchange
# Perform incremental backups (set to "strict" if you want to prevent
# creation of non-incremental backups if no parent is found).
#incremental yes
# Specify after what time (in full hours after midnight) backups/
# snapshots are considered as a daily backup/snapshot
#preserve_hour_of_day 0
# Specify on which day of week weekly/monthly backups are to be
# preserved.
#preserve_day_of_week sunday
# Preserve all snapshots for a minimum period of time.
#snapshot_preserve_min 1d
# Retention policy for the source snapshots.
#snapshot_preserve <NN>h <NN>d <NN>w <NN>m <NN>y
# Preserve all backup targets for a minimum period of time.
#target_preserve_min no
# Retention policy for backup targets:
#target_preserve <NN>h <NN>d <NN>w <NN>m <NN>y
# Retention policy for archives ("btrbk archive" command):
#archive_preserve_min no
#archive_preserve <NN>h <NN>d <NN>w <NN>m <NN>y
# Enable compression for remote btrfs send/receive operations:
#stream_compress no
#stream_compress_level default
#stream_compress_threads default
# Enable lock file support: Ensures that only one instance of btrbk
# can be run at a time.
lockfile /var/lock/btrbk.lock
# Don't wait for transaction commit on deletion. Enable this to make
# sure the deletion of subvolumes is committed to disk when btrbk
# terminates.
#btrfs_commit_delete no
#
# Volume section (optional): "volume <volume-directory>"
#
# <volume-directory> Base path within a btrfs filesystem
# containing the subvolumes to be backuped
# (usually the mount-point of a btrfs filesystem
# mounted with subvolid=5 option).
#
# Subvolume section: "subvolume <subvolume-name>"
#
# <subvolume-name> Subvolume to be backuped, relative to
# <volume-directory> in volume section.
#
# Target section: "target <type> <volume-directory>"
#
# <type> (optional) type, defaults to "send-receive".
# <volume-directory> Directory within a btrfs filesystem
# receiving the backups.
#
# NOTE: The parser does not care about indentation, this is only for
# human readability. All options apply to the last section
# encountered, overriding the corresponding option of the upper
# section. This means that the global options must be set on top,
# before any "volume", "subvolume" or "target section.
#
#
# Example retention policy:
#
# snapshot_preserve_min 2d
# snapshot_preserve 14d
# target_preserve_min no
# target_preserve 20d 10w *m
snapshot_preserve_min 2d
snapshot_preserve 14d
target_preserve_min no
target_preserve 14d 10w *m
archive_preserve_min latest
archive_preserve 12m 10y
# Global settings
compat_remote busybox
send_protocol 2
# Root backup workaround, omit the volume section
subvolume /
target_preserve 14d 10w 6m
snapshot_dir /.snapshots # Absolute path to snapshots dir
target /mnt/backup/workstation/root
# target ssh://router.lan/mnt/backup/workstation/root
# target /run/media/bryan/backup/workstation/root
# target ssh://home-router/mnt/backup/workstation/root
volume /home
subvolume bryan
target /mnt/backup/workstation/home
# target ssh://router.lan/mnt/backup/workstation/home
target_preserve 14d 10w 6m
# target ssh://home-router/mnt/backup/workstation/home
# target /run/media/bryan/backup/workstation/home
volume /mnt/downloads
subvolume downloads
target /mnt/backup/workstation/downloads
# target /run/media/bryan/backup/workstation/downloads
volume /
subvolume /mnt/ebooks
target /mnt/backup/media
subvolume /mnt/cover-art
target /mnt/backup/media
# target ssh://router.lan/mnt/backup/media
# target ssh://home-router/mnt/backup/media
volume /mnt/array/media
target /mnt/backup/media
# target ssh://router.lan/mnt/backup/media
# target ssh://home-router/mnt/backup/media
subvolume pictures
subvolume music
target_preserve_min all # for home-router to keep samba share (and safer overall)
# #
# # Simple setup: Backup root and home to external disk
# #
# snapshot_dir /btrbk_snapshots
# target /mnt/btr_backup
# subvolume /
# subvolume /home
# #
# # Complex setup
# #
# # In order to keep things organized, it is recommended to use "volume"
# # sections and mount the top-level subvolume (subvolid=5):
# #
# # $ mount -o subvolid=5 /dev/sda1 /mnt/btr_pool
# #
# # Backup to external disk mounted on /mnt/btr_backup
# volume /mnt/btr_pool
# # Create snapshots in /mnt/btr_pool/btrbk_snapshots
# snapshot_dir btrbk_snapshots
# # Target for all subvolume sections:
# target /mnt/btr_backup
# # Some default btrfs installations (e.g. Ubuntu) use "@" for rootfs
# # (mounted at "/") and "@home" (mounted at "/home"). Note that this
# # is only a naming convention.
# #subvolume @
# subvolume root
# subvolume home
# subvolume kvm
# # Use different retention policy for kvm backups:
# target_preserve 7d 4w
# # Backup data to external disk as well as remote host
# volume /mnt/btr_data
# subvolume data
# # Always create snapshot, even if targets are unreachable
# snapshot_create always
# target /mnt/btr_backup
# target ssh://backup.my-remote-host.com/mnt/btr_backup
# # Backup from remote host, with different naming
# volume ssh://my-remote-host.com/mnt/btr_pool
# subvolume data_0
# snapshot_dir snapshots/btrbk
# snapshot_name data_main
# target /mnt/btr_backup/my-remote-host.com
# # Backup on demand (noauto) to remote host running busybox, login as
# # regular user using ssh-agent with current user name (ssh_user no)
# # and default credentials (ssh_identity no).
# volume /home
# noauto yes
# compat busybox
# backend_remote btrfs-progs-sudo
# ssh_user no
# ssh_identity no
# target ssh://my-user-host.com/mnt/btr_backup/home
# subvolume alice
# subvolume bob
# # Resume backups from remote host which runs its own btrbk instance
# # creating snapshots for "home" in "/mnt/btr_pool/btrbk_snapshots".
# volume ssh://my-remote-host.com/mnt/btr_pool
# snapshot_dir btrbk_snapshots
# snapshot_create no
# snapshot_preserve_min all
# subvolume home
# target /mnt/btr_backup/my-remote-host.com

View File

@@ -1,131 +0,0 @@
#
# pwrstatd configuration file
#
# You must restart pwrstatd after changing this file in order for changes to take effect.
# Ex:/etc/init.d/pwrstatd restart
#
# Action setting for event of Power Failure
#
# A delay time in seconds since event of Power Failure occur then to run shell
# script and shutdown system. Allowed range is 0 ~ 3600. Default is 60 sec.
powerfail-delay = 60
# Enable to run shell script when the event of Power Failure occur.
# The allowed options are yes and no. Default is yes.
powerfail-active = no
# Assign a path of script file for event of Power Failure.
# The default is /etc/pwrstatd-powerfail.sh
powerfail-cmd-path = /etc/pwrstatd-powerfail.sh
# How much time in seconds to take script running for event of Power Failure.
# The allowed range is 0 ~ 3600. Default is 0 sec.
powerfail-duration = 0
# Allow Daemon to shutdown system for event of Power Failure.
# The allowed options are yes and no. Default is yes.
powerfail-shutdown = no
#
# Action setting for event of Battery Low
#
# A threshold of Battery Capacity, If the battery capacity is lower than this
# value and a event of Battery Low will be identified. The unit is percentage.
# The allowed range is 0 ~ 90. Default is 35 %.
lowbatt-threshold = 10
# A threshold of Remaining Runtime, If the Remaining Runtime is lower than this
# value and a event of Battery Low will be identified. The unit is second.
# The allowed range is 0 ~ 3600. Default is 300 sec.
# Note: When meet this condition the below 'shutdown-sustain' property
# will be ignored.
runtime-threshold = 180
# Enable to run shell script when the event of Battery Low occur.
# The allowed options are yes and no. Default is yes.
lowbatt-active = no
# Assign a path of script file for event of Battery Low.
# The default is /etc/pwrstatd-lowbatt.sh
lowbatt-cmd-path = /etc/pwrstatd-lowbatt.sh
# How much time in seconds to take script running for event of Battery Low.
# The allowed range is 0 ~ 60. Default is 0 sec.
lowbatt-duration = 0
# Allow Daemon to shutdown system for event of Battery Low.
# The allowed options are yes and no. Default is yes.
lowbatt-shutdown = yes
# Turn UPS alarm on or off.
# The allowed options are yes and no. Default is yes.
enable-alarm = yes
# The necessary time in seconds for system shutdown.
# The UPS will turn power off when this time is expired.
# The allowed range is 0 ~ 3600. Default is 600 sec.(10 min.)
# If the computer shutdown is cause by low runtime condition, the UPS will
# turn power off when the time is expired that time is assigned on
# 'runtime-threshold' property and it is no longer to refer the
# 'shutdown-sustain' property.
shutdown-sustain = 0
# Daemon will turn UPS power off once it ask system shutdown cause by a power
# event. Allowed options are yes and no. Default is yes.
turn-ups-off = no
# The period of polling UPS in seconds.
# The allowed range is 1 ~ 60. Default is 3 sec.
ups-polling-rate = 5
# the period of re-try to find available UPS in seconds since find nothing at
# last time. The allowed range is 1 ~ 300. Default is 10 sec.
ups-retry-rate = 10
# Prohibiting daemon to provide communication mechanism for client, such as
# pwrstat command. normally, it should be 'no'. It can be 'yes' if any security
# consideration. Allowed options are yes and no. Default is no.
prohibit-client-access = no
# The pwrstatd accepts four types of device node which includes the 'ttyS',
# 'ttyUSB', 'hiddev', and 'libusb' for communication with UPS. The pwrstatd
# defaults to enumerate all acceptable device nodes and pick up to use an
# available device node automatically. But this may cause a disturbance to the
# device node which is occupied by other software. Therefore, you can restrict
# this enumerate behave by using allowed-device-nodes option. You can assign
# the single device node path or multiple device node paths divided by a
# semicolon at this option. All groups of 'ttyS', 'ttyUSB', 'hiddev', or
# 'libusb' device node are enumerated without a suffix number assignment.
# Note, the 'libusb' does not support suffix number only.
#
# For example: restrict to use ttyS1, ttyS2 and hiddev1 device nodes at /dev
# path only.
# allowed-device-nodes = /dev/ttyS1;/dev/ttyS2;/dev/hiddev1
#
# For example: restrict to use ttyS and ttyUSB two groups of device node at
# /dev,/dev/usb, and /dev/usb/hid paths(includes ttyS0 to ttySN and ttyUSB0 to
# ttyUSBN, N is number).
# allowed-device-nodes = ttyS;ttyUSB
#
# For example: restrict to use hiddev group of device node at /dev,/dev/usb,
# and /dev/usb/hid paths(includes hiddev0 to hiddevN, N is number).
# allowed-device-nodes = hiddev
#
# For example: restrict to use libusb device.
# allowed-device-nodes = libusb
allowed-device-nodes =
# Daemon will hibernate system to instead of system shutdown when power
# event occur. Allowed options are yes and no. Default is no.
hibernate = no
# Enable cloud solution.
# The allowed options are yes and no. Default is no.
cloud-active = no
# Account for cloud server login.
cloud-account =

170
group_vars/all/apps.yml Normal file
View File

@@ -0,0 +1,170 @@
---
apps:
aichat:
cargo: [aichat]
directories:
- dest: "{{ ansible_facts['env']['HOME'] }}/.config/aichat"
mode: '0755'
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
configs:
- dest: "{{ ansible_facts['env']['HOME'] }}/.config/aichat/config.yaml"
mode: '0600'
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
content: |
# see https://github.com/sigoden/aichat/blob/main/config.example.yaml
model: claude:claude-haiku-4-5-20251001
clients:
- type: claude
api_key: {{ ANTHROPIC_API_KEY }}
btrfsmaintenance:
packages: [btrfsmaintenance]
services_system_enabled: [btrfs-balance.timer, btrfs-scrub.timer, btrfs-trim.timer]
directories:
- dest: /etc/sysconfig
mode: '0755'
owner: root
group: root
configs:
- dest: /etc/sysconfig/btrfsmaintenance
mode: '0644'
owner: root
group: root
content: |
BTRFS_LOG_OUTPUT="journal"
BTRFS_DEFRAG_PATHS=""
BTRFS_DEFRAG_PERIOD="none"
BTRFS_DEFRAG_MIN_SIZE="+1M"
BTRFS_BALANCE_MOUNTPOINTS="auto"
BTRFS_BALANCE_PERIOD="weekly"
BTRFS_BALANCE_DUSAGE="0 20 50 80"
BTRFS_BALANCE_MUSAGE="80"
BTRFS_SCRUB_MOUNTPOINTS="auto"
BTRFS_SCRUB_PERIOD="quarterly"
BTRFS_SCRUB_PRIORITY="idle"
BTRFS_SCRUB_READ_ONLY="false"
BTRFS_TRIM_PERIOD="weekly"
BTRFS_TRIM_MOUNTPOINTS="auto"
BTRFS_ALLOW_CONCURRENCY="false"
code:
packages: [code]
dnf_repos_add:
- name: code
description: Visual Studio Code
baseurl: https://packages.microsoft.com/yumrepos/vscode
gpgkey: https://packages.microsoft.com/keys/microsoft.asc
dnf:
packages: [dnf]
configs:
- dest: /etc/dnf/dnf.conf
mode: '0644'
owner: root
group: root
content: |
[main]
# installonly_limit=3
# best=False
skip_if_unavailable=True
deltarpm=True
fastestmirror=True
dnf-automatic:
packages: [dnf-automatic]
services_system_enabled: [dnf-automatic.timer]
installJRMC:
git:
- repo: https://git.bryanroessler.com/bryan/installJRMC.git
dest: "{{ ansible_facts['env']['HOME'] }}/.local/bin/installJRMC"
version: dev
mediacenter35:
packages: [mediacenter35]
dnf_repos_add:
- name: mediacenter35
description: JRiver Media Center hosted by BryanC
baseurl: https://repos.bryanroessler.com/jriver
gpgcheck: false
mullvad-vpn:
packages: [mullvad-vpn]
dnf_repofiles_add: [https://repository.mullvad.net/rpm/stable/mullvad.repo]
openwrtbuilder:
git:
- repo: https://git.bryanroessler.com/bryan/openwrtbuilder.git
dest: "{{ ansible_facts['env']['HOME'] }}/.local/bin/openwrtbuilder"
version: dev
profile-sync-daemon:
packages: [profile-sync-daemon]
services_system_enabled: [psd.service]
rpmfusion-free-release:
packages: [rpmfusion-free-release]
dnf_install_remote: ["https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-{{ ansible_facts['distribution_major_version'] }}.noarch.rpm"]
openssh-server:
packages: [openssh-server]
services_system_enabled: [sshd.service]
syncthing:
packages: [syncthing]
services_user_enabled: [syncthing.service]
tailscale:
packages: [tailscale]
dnf_repofiles_add: [https://pkgs.tailscale.com/stable/fedora/tailscale.repo]
services_system_enabled: [tailscaled.service]
zsh-completions:
packages: [zsh-completions]
dnf_repos_add:
- name: zsh-completions
description: zsh-completions from openSUSE
baseurl: https://download.opensuse.org/repositories/shells:zsh-users:zsh-completions/Fedora_Rawhide/
gpgkey: https://download.opensuse.org/repositories/shells:zsh-users:zsh-completions/Fedora_Rawhide/repodata/repomd.xml.key
dnf_install:
- btrfs-assistant
- calibre
- cargo
- createrepo_c
- firefox
- flatpak
- gettext
- htop
- iperf3
- nautilus-python
- pinta
- python3-psutil
- python3-virtualenv
- python3-virtualenvwrapper
- remmina
- setroubleshoot
- ShellCheck
- snapd
- toolbox
- vim
- wl-clipboard
- zsh
dnf_remove:
- abrt
- anaconda-live
- gnome-initial-setup
- gnome-software
- open-vm-tools-desktop
- orca
- rhythmbox
dnf_repos_remove:
- google-chrome
services_system_disabled:
- raid-check.timer
- fstrim.timer

View File

@@ -1,20 +0,0 @@
---
symlinks:
- name: develop
src: "{{ lookup('env', 'HOME') }}/documents/develop"
dest: "{{ lookup('env', 'HOME') }}/develop"
- name: music
src: "{{ lookup('env', 'HOME') }}/media/music"
dest: "{{ lookup('env', 'HOME') }}/music"
- name: pictures
src: "{{ lookup('env', 'HOME') }}/media/pictures"
dest: "{{ lookup('env', 'HOME') }}/pictures"
- name: videos
src: "{{ lookup('env', 'HOME') }}/media/videos"
dest: "{{ lookup('env', 'HOME') }}/videos"
- name: ebooks
src: "{{ lookup('env', 'HOME') }}/media/ebooks"
dest: "{{ lookup('env', 'HOME') }}/ebooks"
- name: bin
src: "{{ lookup('env', 'HOME') }}/.local/bin"
dest: "{{ lookup('env', 'HOME') }}/bin"

View File

@@ -1,4 +0,0 @@
---
directories:
- path: "{{ ansible_facts['env']['HOME'] }}/.local/bin"
mode: '0755'

View File

@@ -1,16 +0,0 @@
---
services_system_enabled:
- dnf-automatic.timer
- btrfs-balance.timer
- btrfs-scrub.timer
- btrfs-trim.timer
- btrbk.timer
- tailscaled
services_user_enabled:
- psd
services_system_disabled:
- qemu-guest-agent
- raid-check.timer
- fstrim.timer

View File

@@ -1,81 +0,0 @@
---
dnf_add_repos:
- name: zsh-completions
description: zsh-completions from openSUSE
baseurl: https://download.opensuse.org/repositories/shells:zsh-users:zsh-completions/Fedora_Rawhide/
gpgkey: https://download.opensuse.org/repositories/shells:zsh-users:zsh-completions/Fedora_Rawhide/repodata/repomd.xml.key
- name: code
description: Visual Studio Code
baseurl: https://packages.microsoft.com/yumrepos/vscode
gpgkey: https://packages.microsoft.com/keys/microsoft.asc
- name: jriver
description: JRiver Media Center by BryanC
baseurl: https://repos.bryanroessler.com/jriver
gpgcheck: false
dnf_add_repofiles:
- name: Mullvad VPN
url: https://repository.mullvad.net/rpm/stable/mullvad.repo
dnf_remove_repos:
- google-chrome
dnf_remove:
- abrt
- anaconda-live
- gnome-initial-setup
- gnome-software
- open-vm-tools-desktop
- orca
- rhythmbox
dnf_install:
- btrbk
- btrfs-assistant
- btrfsmaintenance
- calibre
- cargo
- code
- dnf-automatic
- flatpak
- gettext
- gnome-tweaks
- htop
- mediacenter35
- mullvad-vpn
- nautilus-python
- pinta
- profile-sync-daemon
- python3-virtualenv
- python3-virtualenvwrapper
- remmina
- rpmfusion-free-release
- setroubleshoot
- ShellCheck
- snapd
- syncthing
- tailscale
- toolbox
- vim
- wl-clipboard
- zsh
- zsh-completions
# Cargo packages to install
cargo_packages:
- aichat
# Git repositories to clone
git_add_repos:
- repo: https://git.bryanroessler.com/bryan/installJRMC.git
dest: "{{ ansible_facts['env']['HOME'] }}/.local/bin/installJRMC"
version: dev
- repo: https://git.bryanroessler.com/bryan/openwrtbuilder.git
dest: "{{ ansible_facts['env']['HOME'] }}/.local/bin/openwrtbuilder"
version: dev
# - repo: https://git.bryanroessler.com/bryan/deployer.git
# dest: "{{ ansible_facts['env']['HOME'] }}/.local/bin/deployer"
# version: dev
# - repo: https://git.bryanroessler.com/bryan/deploy.git
# dest: "{{ ansible_facts['env']['HOME'] }}/.local/bin/deploy"
# version: dev

View File

@@ -1,21 +0,0 @@
---
# GNOME settings via gsettings
sysconfig_gsettings:
- schema: org.gnome.nautilus.preferences
key: always-use-location-entry
value: "true"
# Sysctl configurations
sysconfig_sysctl:
- name: fs.inotify.max_user_watches
value: 524288
file: /etc/sysctl.d/local.conf
# Sudoers configuration - commands that can run without password
sysconfig_sudoers_nopasswd_commands:
- /usr/bin/psd-overlay-helper
- /usr/sbin/btrfs
- /usr/bin/journalctl
- /usr/bin/dnf
- /usr/bin/fwupdmgr
- /usr/bin/dmesg

View File

@@ -0,0 +1,51 @@
apps_group:
keyd:
copr: [alternateved/keyd]
packages: [keyd]
services_system_enabled: [keyd.service]
configs:
- dest: /etc/keyd/default.conf
mode: '0644'
owner: root
group: root
content: |
[ids]
0001:0001:09b4e68d
[main]
leftmeta+leftshift+f23 = rightcontrol
btrbk:
packages: [btrbk]
services_system_enabled: [btrbk.timer]
configs:
- dest: /etc/btrbk/btrbk.conf
mode: '0644'
owner: root
group: root
content: |
transaction_log /var/log/btrbk.log
ssh_identity /root/.ssh/id_ed25519
ssh_user root
backend_local_user btrfs-progs-sudo
stream_buffer 256m
snapshot_dir .snapshots
snapshot_create onchange
lockfile /var/lock/btrbk.lock
snapshot_preserve_min 2d
snapshot_preserve 14d
target_preserve_min no
target_preserve 14d 10w *m
archive_preserve_min latest
archive_preserve 12m 10y
subvolume /
target_preserve 14d 10w 6m
snapshot_dir /.snapshots
target ssh://workstation/mnt/backup/laptop/root
volume /home
subvolume bryan
target ssh://workstation/mnt/backup/laptop/home

View File

@@ -1,5 +1,5 @@
--- ---
mounts: filesystems_mounts:
- path: /home - path: /home
src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83 src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83
fstype: btrfs fstype: btrfs
@@ -9,3 +9,27 @@ mounts:
group: root group: root
mode: '0755' mode: '0755'
create_dir: false create_dir: false
filesystem_directories:
- path: "{{ ansible_facts['env']['HOME'] }}/.local/bin"
mode: '0755'
filesystem_symlinks:
- name: develop
src: "{{ lookup('env', 'HOME') }}/documents/develop"
dest: "{{ lookup('env', 'HOME') }}/develop"
- name: music
src: "{{ lookup('env', 'HOME') }}/media/music"
dest: "{{ lookup('env', 'HOME') }}/music"
- name: pictures
src: "{{ lookup('env', 'HOME') }}/media/pictures"
dest: "{{ lookup('env', 'HOME') }}/pictures"
- name: videos
src: "{{ lookup('env', 'HOME') }}/media/videos"
dest: "{{ lookup('env', 'HOME') }}/videos"
- name: ebooks
src: "{{ lookup('env', 'HOME') }}/media/ebooks"
dest: "{{ lookup('env', 'HOME') }}/ebooks"
- name: bin
src: "{{ lookup('env', 'HOME') }}/.local/bin"
dest: "{{ lookup('env', 'HOME') }}/bin"

View File

@@ -1,3 +0,0 @@
---
services_system_enabled_group:
- keyd

View File

@@ -1,7 +0,0 @@
---
dnf_add_copr_group:
- alternateved/keyd
dnf_install_group:
- keyd

View File

@@ -0,0 +1,88 @@
---
# Workstation-specific apps metadata
apps_group:
pwrstatd:
packages_remote: ["https://dl4jz3rbrsfum.cloudfront.net/software/PPL_64bit_v1.4.1.rpm"]
packages: [powerpanel]
services_system_enabled: [pwrstatd.service]
directories:
- dest: /etc/init.d
owner: root
group: root
configs:
- dest: /etc/pwrstatd.conf
mode: '0644'
owner: root
group: root
content: |
powerfail-delay = 60
powerfail-active = no
powerfail-cmd-path = /etc/pwrstatd-powerfail.sh
powerfail-duration = 0
powerfail-shutdown = no
lowbatt-threshold = 10
runtime-threshold = 180
lowbatt-active = no
lowbatt-cmd-path = /etc/pwrstatd-lowbatt.sh
lowbatt-duration = 0
lowbatt-shutdown = yes
enable-alarm = yes
shutdown-sustain = 0
turn-ups-off = no
ups-polling-rate = 5
ups-retry-rate = 10
prohibit-client-access = no
allowed-device-nodes =
hibernate = no
cloud-active = no
cloud-account =
btrbk:
packages: [btrbk]
services_system_enabled: [btrbk.timer]
configs:
- dest: /etc/btrbk/btrbk.conf
mode: '0644'
owner: root
group: root
content: |
transaction_log /var/log/btrbk.log
ssh_identity /home/bryan/.config/btrbk/id_ed25519
ssh_user root
backend_local_user btrfs-progs-sudo
stream_buffer 256m
snapshot_dir .snapshots
snapshot_create onchange
lockfile /var/lock/btrbk.lock
snapshot_preserve_min 2d
snapshot_preserve 14d
target_preserve_min no
target_preserve 14d 10w *m
archive_preserve_min latest
archive_preserve 12m 10y
subvolume /
target_preserve 14d 10w 6m
snapshot_dir /.snapshots
target /mnt/backup/workstation/root
volume /home
subvolume bryan
target /mnt/backup/workstation/home
target_preserve 14d 10w 6m
volume /mnt/downloads
subvolume downloads
target /mnt/backup/workstation/downloads
volume /
subvolume /mnt/ebooks
target /mnt/backup/media
subvolume /mnt/cover-art
target /mnt/backup/media
volume /mnt/array/media
target /mnt/backup/media
subvolume pictures
subvolume music

View File

@@ -1,9 +1,9 @@
--- ---
mounts: filesystem_mounts:
- path: /home - path: /home
src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83 src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83
fstype: btrfs fstype: btrfs
opts: subvol=home,compress=zstd:1,defaults opts: "subvol=home,compress=zstd:1,defaults"
state: mounted state: mounted
owner: root owner: root
group: root group: root
@@ -13,17 +13,17 @@ mounts:
- path: /mnt/array - path: /mnt/array
src: /dev/disk/by-uuid/36fe5749-800a-4ab5-a89a-6ad343f5d42f src: /dev/disk/by-uuid/36fe5749-800a-4ab5-a89a-6ad343f5d42f
fstype: btrfs fstype: btrfs
opts: defaults,compress=zstd:1,x-systemd.device-timeout=180s opts: "defaults,compress=zstd:1,x-systemd.device-timeout=180s"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /mnt/backup - path: /mnt/backup
src: /dev/disk/by-uuid/64cc836d-e55f-4c34-83db-01c9b43c218a src: /dev/disk/by-uuid/64cc836d-e55f-4c34-83db-01c9b43c218a
fstype: btrfs fstype: btrfs
opts: defaults,compress=zstd:1,x-systemd.device-timeout=180s,nofail opts: "defaults,compress=zstd:1,x-systemd.device-timeout=180s,nofail"
state: mounted state: mounted
owner: root owner: root
group: root group: root
@@ -31,72 +31,72 @@ mounts:
create_dir: false create_dir: false
- path: /mnt/downloads - path: /mnt/downloads
src: /dev/disk/by-uuid/ee6247ed-5bcf-481e-802e-74efbc02eb45 src: /dev/disk/by-uuid/56a4fe2f-ce26-48cc-b602-548db7357549
fstype: btrfs fstype: btrfs
opts: defaults,compress=zstd:1 opts: "defaults,compress=zstd:1"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /home/bryan/downloads - path: /home/"{{ ansible_user }}"/downloads
src: /dev/disk/by-uuid/ee6247ed-5bcf-481e-802e-74efbc02eb45 src: /dev/disk/by-uuid/56a4fe2f-ce26-48cc-b602-548db7357549
fstype: btrfs fstype: btrfs
opts: subvol=downloads,defaults,compress=zstd:1,x-systemd.requires=home.mount,x-gvfs-hide opts: "subvol=downloads,defaults,compress=zstd:1,x-systemd.requires=home.mount,x-gvfs-hide"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /home/bryan/media - path: "/home/{{ ansible_user }}/media"
src: /dev/disk/by-uuid/36fe5749-800a-4ab5-a89a-6ad343f5d42f src: /dev/disk/by-uuid/36fe5749-800a-4ab5-a89a-6ad343f5d42f
fstype: btrfs fstype: btrfs
opts: subvol=media,defaults,compress=zstd:1,x-systemd.requires=home.mount,x-systemd.device-timeout=180s,x-gvfs-hide opts: "subvol=media,defaults,compress=zstd:1,x-systemd.requires=home.mount,x-systemd.device-timeout=180s,x-gvfs-hide"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /mnt/array/media/cover-art - path: /mnt/array/media/cover-art
src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83 src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83
fstype: btrfs fstype: btrfs
opts: subvol=root/mnt/cover-art,defaults,compress=zstd:1,x-systemd.requires=mnt-array.mount,x-gvfs-hide opts: "subvol=root/mnt/cover-art,defaults,compress=zstd:1,x-systemd.requires=mnt-array.mount,x-gvfs-hide"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /home/bryan/media/cover-art - path: "/home/{{ ansible_user }}/media/cover-art"
src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83 src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83
fstype: btrfs fstype: btrfs
opts: subvol=root/mnt/cover-art,defaults,compress=zstd:1,x-systemd.requires=home-bryan-media.mount,x-gvfs-hide opts: "subvol=root/mnt/cover-art,defaults,compress=zstd:1,x-systemd.requires=home-{{ ansible_user }}-media.mount,x-gvfs-hide"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /mnt/array/media/ebooks - path: /mnt/array/media/ebooks
src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83 src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83
fstype: btrfs fstype: btrfs
opts: subvol=root/mnt/ebooks,defaults,compress=zstd:1,x-systemd.requires=mnt-array.mount,x-gvfs-hide opts: "subvol=root/mnt/ebooks,defaults,compress=zstd:1,x-systemd.requires=mnt-array.mount,x-gvfs-hide"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /home/bryan/media/ebooks - path: "/home/{{ ansible_user }}/media/ebooks"
src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83 src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83
fstype: btrfs fstype: btrfs
opts: subvol=root/mnt/ebooks,defaults,compress=zstd:1,x-systemd.requires=home-bryan-media.mount,x-gvfs-hide opts: "subvol=root/mnt/ebooks,defaults,compress=zstd:1,x-systemd.requires=home-{{ ansible_user }}-media.mount,x-gvfs-hide"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
@@ -105,27 +105,53 @@ mounts:
fstype: btrfs fstype: btrfs
opts: subvol=root/mnt/screenshots,defaults,compress=zstd:1,x-systemd.requires=mnt-array.mount,x-gvfs-hide opts: subvol=root/mnt/screenshots,defaults,compress=zstd:1,x-systemd.requires=mnt-array.mount,x-gvfs-hide
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /home/bryan/media/pictures/Screenshots - path: "/home/{{ ansible_user }}/media/pictures/Screenshots"
src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83 src: /dev/disk/by-uuid/42f5911d-d634-4f92-9561-c7e20ca66c83
fstype: btrfs fstype: btrfs
opts: subvol=root/mnt/screenshots,defaults,compress=zstd:1,x-systemd.requires=home-bryan-media.mount,x-gvfs-hide opts: "subvol=root/mnt/screenshots,defaults,compress=zstd:1,x-systemd.requires=home-{{ ansible_user }}-media.mount,x-gvfs-hide"
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
- path: /home/bryan/devices/laptop/music - path: "/home/{{ ansible_user }}/devices/laptop/music"
src: /dev/disk/by-uuid/d0ed963e-aaa0-4dcc-9ece-4ea8fe7fcea2 src: /dev/disk/by-uuid/d0ed963e-aaa0-4dcc-9ece-4ea8fe7fcea2
fstype: btrfs fstype: btrfs
opts: defaults,compress=zstd:1,x-systemd.requires=home.mount,x-gvfs-hide opts: defaults,compress=zstd:1,x-systemd.requires=home.mount,x-gvfs-hide
state: mounted state: mounted
owner: bryan owner: "{{ ansible_user }}"
group: bryan group: "{{ ansible_user }}"
mode: '0755' mode: '0755'
create_dir: false create_dir: false
filesystem_directories:
- path: "/home/{{ ansible_user }}/.local/bin"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
filesystem_symlinks:
- name: develop
src: "/home/{{ ansible_user }}/documents/develop"
dest: "/home/{{ ansible_user }}/develop"
- name: music
src: "/home/{{ ansible_user }}/media/music"
dest: "/home/{{ ansible_user }}/music"
- name: pictures
src: "/home/{{ ansible_user }}/media/pictures"
dest: "/home/{{ ansible_user }}/pictures"
- name: videos
src: "/home/{{ ansible_user }}/media/videos"
dest: "/home/{{ ansible_user }}/videos"
- name: ebooks
src: "/home/{{ ansible_user }}/media/ebooks"
dest: "/home/{{ ansible_user }}/ebooks"
- name: bin
src: "/home/{{ ansible_user }}/.local/bin"
dest: "/home/{{ ansible_user }}/bin"

View File

@@ -0,0 +1,45 @@
---
quadlets_deploy_configs: true # deploy quadlet service configs
maintainer: "Bryan C. Roessler"
tz: "America/New_York"
alpine_base_image: "docker.io/alpine:3.23"
alpine_mirror: "https://mirror.pilotfiber.com/alpinelinux"
# Directories
config_root: "{{ lookup('env', 'HOME') }}/.config"
download_root: "{{ lookup('env', 'HOME') }}/downloads"
monitor_root: "{{ lookup('env', 'HOME') }}/downloads"
media_root: "{{ lookup('env', 'HOME') }}/media"
# linux-system-roles.podman
podman_create_host_directories: true
podman_host_directories:
DEFAULT:
owner: "{{ lookup('env', 'USER') }}"
group: "{{ lookup('env', 'USER') }}"
mode: "0755"
podman_activate_systemd_units: true
podman_run_as_user: "{{ lookup('env', 'USER') }}"
podman_run_as_group: "{{ lookup('env', 'USER') }}"
podman_systemd_unit_scope: user
podman_prune_images: true
podman_pull_image: false
podman_continue_if_pull_fails: false
# Pod definitions
pods:
- name: htpc
network: pasta
publish:
- "8080:80"
- "8443:443"
containers:
- sabnzbd
- qbittorrent
- sonarr
- radarr
- lidarr
- prowlarr
- unpackerr
- traefik

View File

@@ -1,3 +0,0 @@
---
services_system_group:
- pwrstatd

View File

@@ -1,3 +0,0 @@
---
dnf_install_group:
- "https://dl4jz3rbrsfum.cloudfront.net/software/PPL_64bit_v1.4.1.rpm"

View File

@@ -5,16 +5,18 @@ htpc-remote ansible_host=htpc ansible_user=bryan
[workstation] [workstation]
workstation-local ansible_connection=local ansible_user=bryan workstation-local ansible_connection=local ansible_user=bryan
workstation-remote ansible_host=workstation ansible_user=bryan workstation-remote ansible_host=workstation ansible_user=bryan
workstation-remote-testing ansible_host=vm-fedora43 ansible_user=bryan
[laptop] [laptop]
laptop-local ansible_connection=local ansible_user=bryan laptop-local ansible_connection=local ansible_user=bryan
laptop-remote ansible_host=laptop ansible_user=bryan laptop-remote ansible_host=laptop ansible_user=bryan
laptop-remote-testing ansible_host=192.168.122.95 ansible_user=bryan
[server] [server]
bryanroessler.com ansible_host=bryanroessler.com ansible_user=bryan bryanroessler.com ansible_host=bryanroessler.com ansible_user=bryan
[testing] # [testing]
testing-remote ansible_host=192.168.122.205 ansible_user=bryan # testing-remote ansible_host=192.168.122.205 ansible_user=bryan
# [desktops:children] # [desktops:children]
# workstation # workstation

View File

@@ -5,12 +5,6 @@
- role: filesystems - role: filesystems
tags: ['filesystems'] tags: ['filesystems']
- name: Deploy users
hosts: all
roles:
- role: users
tags: ['users']
- name: Deploy scripts - name: Deploy scripts
hosts: all hosts: all
roles: roles:
@@ -23,20 +17,26 @@
- role: dotfiles - role: dotfiles
tags: ['dotfiles'] tags: ['dotfiles']
- name: Deploy software - name: Deploy apps
hosts: all hosts: all
roles: roles:
- role: software - role: apps
tags: ['software'] tags: ['apps']
- name: Deploy services - name: Deploy users
hosts: all hosts: all
roles: roles:
- role: services - role: users
tags: ['services'] tags: ['users']
- name: Deploy sysconfig - name: Deploy sysconfig
hosts: all hosts: all
roles: roles:
- role: sysconfig - role: sysconfig
tags: ['sysconfig'] tags: ['sysconfig']
- name: Deploy quadlets
hosts: all
roles:
- role: quadlets
tags: ['quadlets']

0
roles/apps/README.md Normal file
View File

View File

@@ -0,0 +1,5 @@
- name: Install cargo packages
community.general.cargo:
name: "{{ item }}"
state: present
loop: "{{ apps_cargo | default([]) + (cargo_install | default([])) }}"

View File

@@ -0,0 +1,31 @@
- name: Create app directories
ansible.builtin.file:
path: "{{ item.dest }}"
state: directory
mode: "0755"
owner: "{{ item.owner | default(omit) }}"
group: "{{ item.group | default(omit) }}"
loop: "{{ apps_directories }}"
become: "{{ item.owner is defined and item.owner == 'root' }}"
when: apps_directories is defined and apps_directories | length > 0
- name: Create parent directories for config files
ansible.builtin.file:
path: "{{ item.dest | dirname }}"
state: directory
mode: '0755'
loop: "{{ apps_configs }}"
become: "{{ item.owner is defined and item.owner == 'root' }}"
when: apps_configs is defined and apps_configs | length > 0
- name: Deploy configuration templates
ansible.builtin.copy:
content: "{{ item.content }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode | default('0644') }}"
owner: "{{ item.owner | default(omit) }}"
group: "{{ item.group | default(omit) }}"
backup: "{{ item.backup | default(false) }}"
loop: "{{ apps_configs }}"
become: "{{ item.owner is defined and item.owner == 'root' }}"
when: apps_configs is defined and apps_configs | length > 0

69
roles/apps/tasks/dnf.yml Normal file
View File

@@ -0,0 +1,69 @@
---
- name: Enable COPR repositories
community.general.copr:
name: "{{ item.repo | default(item) }}"
state: enabled
loop: "{{ (copr_install | default([])) + (apps_copr | default([])) }}"
become: true
- name: Add DNF repositories
ansible.builtin.yum_repository:
name: "{{ item.name }}"
description: "{{ item.description | default(omit) }}"
baseurl: "{{ item.baseurl | default(omit) }}"
metalink: "{{ item.metalink | default(omit) }}"
enabled: "{{ item.enabled | default(true) }}"
metadata_expire: "{{ item.metadata_expire | default(omit) }}"
gpgcheck: "{{ item.gpgcheck | default(true) }}"
repo_gpgcheck: "{{ item.repo_gpgcheck | default(omit) }}"
gpgkey: "{{ item.gpgkey | default(omit) }}"
loop: "{{ (dnf_repos_add | default([])) + (apps_dnf_repos_add | default([])) }}"
become: true
- name: Add DNF repository files
ansible.builtin.get_url:
url: "{{ item.url | default(item) }}"
dest: "/etc/yum.repos.d/{{ (item.url | default(item)) | basename }}"
owner: root
group: root
mode: '0644'
loop: "{{ (dnf_repofiles_add | default([])) + (apps_dnf_repofiles_add | default([])) }}"
become: true
- name: Remove DNF repositories
ansible.builtin.yum_repository:
name: "{{ item }}"
state: absent
loop: "{{ dnf_repos_remove | default([]) }}"
become: true
- name: Install remote RPM packages (from URLs)
ansible.builtin.dnf:
name: "{{ (dnf_install_remote | default([])) + (apps_dnf_install_remote | default([])) + (apps_packages_remote | default([])) }}"
state: present
disable_gpg_check: true
become: true
when: ((dnf_install_remote | default([])) + (apps_dnf_install_remote | default([])) + (apps_packages_remote | default([]))) | length > 0
- name: Remove unwanted packages
ansible.builtin.dnf:
name: "{{ item }}"
state: absent
autoremove: true
loop: "{{ dnf_remove | default([]) }}"
failed_when: false
become: true
- name: Install packages
ansible.builtin.dnf:
name: "{{ apps_all_packages }}"
state: present
skip_broken: true
become: true
- name: Update all packages
ansible.builtin.dnf:
name: "*"
state: latest # noqa package-latest
skip_broken: true
become: true

10
roles/apps/tasks/git.yml Normal file
View File

@@ -0,0 +1,10 @@
- name: Clone git repositories
ansible.builtin.git:
repo: "{{ item.repo }}"
dest: "{{ item.dest }}"
version: "{{ item.version }}"
update: true
depth: 1
accept_hostkey: true
loop: "{{ (git | default([])) + (apps_git | default([])) }}"
when: ((git | default([])) + (apps_git | default([]))) | length > 0

39
roles/apps/tasks/main.yml Normal file
View File

@@ -0,0 +1,39 @@
---
# Merge all apps dicts (apps + apps_group)
- name: Merge apps configurations
ansible.builtin.set_fact:
apps_all_apps: "{{ apps | default({}) | combine(apps_group | default({}), recursive=True) }}"
# Extract various lists from merged apps
- name: Build package and config lists
ansible.builtin.set_fact:
apps_packages: "{{ apps_all_apps | dict2items | selectattr('value.packages', 'defined') | map(attribute='value.packages') | flatten }}"
apps_packages_remote: "{{ apps_all_apps | dict2items | selectattr('value.packages_remote', 'defined') | map(attribute='value.packages_remote') | flatten }}"
apps_cargo: "{{ apps_all_apps | dict2items | selectattr('value.cargo', 'defined') | map(attribute='value.cargo') | flatten }}"
apps_copr: "{{ apps_all_apps | dict2items | selectattr('value.copr', 'defined') | map(attribute='value.copr') | flatten }}"
apps_git: "{{ apps_all_apps | dict2items | selectattr('value.git', 'defined') | map(attribute='value.git') | flatten }}"
apps_configs: "{{ apps_all_apps | dict2items | selectattr('value.configs', 'defined') | map(attribute='value.configs') | flatten }}"
apps_directories: "{{ apps_all_apps | dict2items | selectattr('value.directories', 'defined') | map(attribute='value.directories') | flatten }}"
apps_dnf_repos_add: "{{ apps_all_apps | dict2items | selectattr('value.dnf_repos_add', 'defined') | map(attribute='value.dnf_repos_add') | flatten }}"
apps_dnf_repofiles_add: "{{ apps_all_apps | dict2items | selectattr('value.dnf_repofiles_add', 'defined') | map(attribute='value.dnf_repofiles_add') | flatten }}"
apps_dnf_install_remote: "{{ apps_all_apps | dict2items | selectattr('value.dnf_install_remote', 'defined') | map(attribute='value.dnf_install_remote') | flatten }}"
apps_services_system_enabled: "{{ apps_all_apps | dict2items | selectattr('value.services_system_enabled', 'defined') | map(attribute='value.services_system_enabled') | flatten }}"
apps_services_system_disabled: "{{ apps_all_apps | dict2items | selectattr('value.services_system_disabled', 'defined') | map(attribute='value.services_system_disabled') | flatten }}"
apps_services_user_enabled: "{{ apps_all_apps | dict2items | selectattr('value.services_user_enabled', 'defined') | map(attribute='value.services_user_enabled') | flatten }}"
apps_services_user_disabled: "{{ apps_all_apps | dict2items | selectattr('value.services_user_disabled', 'defined') | map(attribute='value.services_user_disabled') | flatten }}"
apps_all_packages: "{{ (apps_packages | default([])) + (dnf_install | default([])) }}"
- name: Deploy repositorioes and packages
ansible.builtin.include_tasks: dnf.yml
- name: Clone git repositories
ansible.builtin.include_tasks: git.yml
- name: Install cargo packages
ansible.builtin.include_tasks: cargo.yml
- name: Deploy app configs
ansible.builtin.include_tasks: configs.yml
- name: Enable and start/stop system and user services
ansible.builtin.include_tasks: services.yml

View File

@@ -1,12 +1,12 @@
---
- name: Enable and start system services - name: Enable and start system services
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ item }}" name: "{{ item }}"
enabled: true enabled: true
state: started state: started
scope: system scope: system
loop: "{{ (services_system_enabled | default([])) + (services_system_enabled_group | default([])) }}" loop: "{{ ((services_system_enabled | default([])) + (apps_services_system_enabled | default([]))) | unique }}"
become: true become: true
failed_when: false
- name: Disable and stop system services - name: Disable and stop system services
ansible.builtin.systemd: ansible.builtin.systemd:
@@ -14,8 +14,9 @@
enabled: false enabled: false
state: stopped state: stopped
scope: system scope: system
loop: "{{ (services_system_disabled | default([])) + (services_system_disabled_group | default([])) }}" loop: "{{ ((services_system_disabled | default([])) + (apps_services_system_disabled | default([]))) | unique }}"
become: true become: true
failed_when: false
- name: Enable and start user services - name: Enable and start user services
ansible.builtin.systemd: ansible.builtin.systemd:
@@ -23,12 +24,14 @@
enabled: true enabled: true
state: started state: started
scope: user scope: user
loop: "{{ (services_user_enabled | default([])) + (services_user_enabled_group | default([])) }}" loop: "{{ ((services_user_enabled | default([])) + (apps_services_user_enabled | default([]))) | unique }}"
failed_when: false
- name: Disable and stop user services - name: Disable and stop user services
ansible.builtin.systemd: ansible.builtin.systemd:
name: "{{ item }}" name: "{{ item }}"
enabled: false enabled: false
state: stopped state: stopped
scope: system scope: user
loop: "{{ (services_user_disabled | default([])) + (services_user_disabled_group | default([])) }}" loop: "{{ ((services_user_disabled | default([])) + (apps_services_user_disabled | default([]))) | unique }}"
failed_when: false

43
roles/dotfiles/README.md Normal file
View File

@@ -0,0 +1,43 @@
# Dotfiles Deployment Guide
Deploy dotfiles using [GNU Stow](https://www.gnu.org/software/stow/).
Each subdirectory is a stow package corresponding to a set of related dotfiles.
## Deploy Dotfiles
Run Stow from within your dotfiles directory:
```sh
cd ~/dotfiles
stow bash
stow vim
stow git
```
This will symlink the files into your `$HOME` directory.
## Deploy All Dotfiles
To deploy all dotfiles at once:
```sh
stow *
```
## Updating Dotfiles
1. Edit files in the respective subdirectories.
2. Commit and push changes as usual with Git.
## Undoing a Stow
To remove symlinks created by Stow:
```sh
stow -D bash
```
---
For more details, see the [GNU Stow manual](https://www.gnu.org/software/stow/manual/stow.html).

View File

@@ -18,8 +18,8 @@ Host home-router
Hostname home-router Hostname home-router
User root User root
Host ax6000 Host w1700k
Hostname ax6000.lan Hostname w1700k.lan
User root User root
Host bryanroessler.com Host bryanroessler.com
@@ -35,8 +35,8 @@ Host workstation
Host laptop Host laptop
Hostname laptop Hostname laptop
Host vm-fedora42 Host vm-fedora43
Hostname vm-fedora42 Hostname 192.168.122.241
Host vm-alma9 Host vm-alma9
Hostname 192.168.122.235 Hostname 192.168.122.235

View File

@@ -1,115 +1,46 @@
--- ---
- name: Find home dotfiles (excluding templates) - name: Discover dotfile packages
ansible.builtin.find: ansible.builtin.find:
paths: paths: "{{ role_path }}"
- "{{ playbook_dir }}/dotfiles/common/home" file_type: directory
- "{{ playbook_dir }}/dotfiles/{{ group_names[0] }}/home" depth: 1
recurse: true excludes: ['tasks', 'templates', 'files', 'vars', 'defaults', 'meta', 'handlers']
file_type: file register: dotfiles_packages
hidden: true
excludes: "*.j2"
delegate_to: localhost delegate_to: localhost
register: dotfiles_home_files
run_once: true
ignore_errors: true
- name: Find root dotfiles (excluding templates) - name: Build complete file list
ansible.builtin.find: ansible.builtin.set_fact:
paths: dotfiles_files: "{{ dotfiles_files | default([]) + lookup('community.general.filetree', item.path, wantlist=True) | selectattr('state', 'equalto', 'file') | list }}"
- "{{ playbook_dir }}/dotfiles/common/root" loop: "{{ dotfiles_packages.files }}"
- "{{ playbook_dir }}/dotfiles/{{ group_names[0] }}/root"
recurse: true
file_type: file
hidden: true
excludes: "*.j2"
delegate_to: localhost delegate_to: localhost
register: dotfiles_root_files
run_once: true
ignore_errors: true
- name: Find home template files - name: Ensure dotfile directories exist
ansible.builtin.find:
paths:
- "{{ playbook_dir }}/dotfiles/common/home"
- "{{ playbook_dir }}/dotfiles/{{ group_names[0] }}/home"
recurse: true
file_type: file
hidden: true
patterns: "*.j2"
delegate_to: localhost
register: dotfiles_home_templates
run_once: true
ignore_errors: true
- name: Find root template files
ansible.builtin.find:
paths:
- "{{ playbook_dir }}/dotfiles/common/root"
- "{{ playbook_dir }}/dotfiles/{{ group_names[0] }}/root"
recurse: true
file_type: file
hidden: true
patterns: "*.j2"
delegate_to: localhost
register: dotfiles_root_templates
run_once: true
ignore_errors: true
- name: Symlink home dotfiles (local)
ansible.builtin.file: ansible.builtin.file:
src: "{{ item.path }}" path: "{{ ansible_facts.env.HOME }}/{{ item.path | dirname }}"
dest: "{{ ansible_facts['env']['HOME'] }}/{{ item.path | regex_replace('^.+/dotfiles/(?:common|' + group_names[0] + ')/home/', '') }}" state: directory
state: link mode: "0755"
force: true loop: "{{ dotfiles_files }}"
loop: "{{ dotfiles_home_files.files }}"
when: ansible_connection in ['local', 'localhost']
- name: Symlink root dotfiles (local) - name: Deploy dotfiles (local with symlinks)
ansible.builtin.file:
src: "{{ item.path }}"
dest: "/{{ item.path | regex_replace('^.+/dotfiles/(?:common|' + group_names[0] + ')/root/', '') }}"
state: link
force: true
loop: "{{ dotfiles_root_files.files }}"
when: ansible_connection in ['local', 'localhost']
become: true
- name: Deploy home dotfiles (remote)
ansible.builtin.copy:
src: "{{ item.path }}"
dest: "{{ ansible_facts['env']['HOME'] }}/{{ item.path | regex_replace('^.+/dotfiles/(?:common|' + group_names[0] + ')/home/', '') }}"
mode: preserve
loop: "{{ dotfiles_home_files.files }}"
when: ansible_connection not in ['local', 'localhost']
- name: Deploy root dotfiles (remote)
ansible.builtin.copy:
src: "{{ item.path }}"
dest: "/{{ item.path | regex_replace('^.+/dotfiles/(?:common|' + group_names[0] + ')/root/', '') }}"
mode: preserve
loop: "{{ dotfiles_root_files.files }}"
when: ansible_connection not in ['local', 'localhost']
become: true
- name: Template home dotfiles
ansible.builtin.template:
src: "{{ item.path }}"
dest: "{{ ansible_facts['env']['HOME'] }}/{{ item.path | regex_replace('^.+/dotfiles/(?:common|' + group_names[0] + ')/home/', '') | replace('.j2', '') }}"
mode: '0600'
loop: "{{ dotfiles_home_templates.files }}"
- name: Template root dotfiles
ansible.builtin.template:
src: "{{ item.path }}"
dest: "/{{ item.path | regex_replace('^.+/dotfiles/(?:common|' + group_names[0] + ')/root/', '') | replace('.j2', '') }}"
mode: '0600'
loop: "{{ dotfiles_root_templates.files }}"
become: true
- name: Deploy symlinks
ansible.builtin.file: ansible.builtin.file:
src: "{{ item.src }}" src: "{{ item.src }}"
dest: "{{ item.dest }}" dest: "{{ ansible_facts.env.HOME }}/{{ item.path }}"
state: link state: link
force: true force: true
loop: "{{ symlinks | default([]) }}" loop: "{{ dotfiles_files | rejectattr('path', 'match', '.*\\.j2$') | list }}"
when: ansible_connection == 'local'
- name: Deploy dotfiles (remote with copy)
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ ansible_facts.env.HOME }}/{{ item.path }}"
mode: preserve
loop: "{{ dotfiles_files | rejectattr('path', 'match', '.*\\.j2$') | list }}"
when: ansible_connection != 'local'
- name: Render dotfile templates
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ ansible_facts.env.HOME }}/{{ item.path | replace('.j2', '') }}"
mode: preserve
loop: "{{ dotfiles_files | selectattr('path', 'match', '.*\\.j2$') | list }}"

View File

@@ -6,7 +6,7 @@
owner: "{{ item.owner | default('root') }}" owner: "{{ item.owner | default('root') }}"
group: "{{ item.group | default('root') }}" group: "{{ item.group | default('root') }}"
mode: "{{ item.mode | default('0755') }}" mode: "{{ item.mode | default('0755') }}"
loop: "{{ mounts | default([]) }}" loop: "{{ filesystem_mounts | default([]) }}"
become: true become: true
when: item.create_dir | default(false) | bool when: item.create_dir | default(false) | bool
@@ -15,7 +15,7 @@
path: "{{ item.path }}" path: "{{ item.path }}"
register: filesystems_mounts_stat register: filesystems_mounts_stat
changed_when: false changed_when: false
loop: "{{ mounts | default([]) }}" loop: "{{ filesystem_mounts | default([]) }}"
- name: Assert mount points exist - name: Assert mount points exist
ansible.builtin.assert: ansible.builtin.assert:
@@ -32,7 +32,7 @@
opts: "{{ item.opts | default('defaults') }}" opts: "{{ item.opts | default('defaults') }}"
state: "{{ item.state | default('mounted') }}" state: "{{ item.state | default('mounted') }}"
backup: true backup: true
loop: "{{ mounts | default([]) }}" loop: "{{ filesystem_mounts | default([]) }}"
become: true become: true
- name: Ensure directories exist - name: Ensure directories exist
@@ -40,4 +40,15 @@
path: "{{ item.path }}" path: "{{ item.path }}"
state: directory state: directory
mode: "{{ item.mode | default('0755') }}" mode: "{{ item.mode | default('0755') }}"
loop: "{{ directories | default([]) }}" owner: "{{ item.owner | default(omit) }}"
group: "{{ item.group | default(omit) }}"
loop: "{{ filesystem_directories | default([]) }}"
become: "{{ (item.owner is defined and item.owner == 'root') }}"
- name: Deploy symlinks
ansible.builtin.file:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
state: link
force: true
loop: "{{ filesystem_symlinks | default([]) }}"

View File

@@ -0,0 +1,145 @@
quadlets:
- name: lazylibrarian
type: container
image: localhost/lazylibrarian:latest
volumes:
- "{{ config_root }}/lazylibrarian:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
- "{{ media_root }}/ebooks:/ebooks:Z"
env:
TZ: "{{ tz }}"
command: ["/venv/bin/python", "/app/LazyLibrarian.py", "--nolaunch", "--port", "{{ lazylibrarian_port | default('5299') }}", "--datadir=/config"]
restart_policy: on-failure
configs:
- path: "{{ config_root }}/lazylibrarian/lazylibrarian.cfg"
template: |
[GENERAL]
homepage = eBooks
auth_type = FORM
displaylength = -1
series_tab = False
config_tab_num = 2
toggles = False
launch_browser = False
imp_preflang = en, en-GB, en-US, eng, English, Unknown
date_lang = en_GB.UTF-8
destination_copy = True
ebook_dir = {{ lazylibrarian_ebook_dir | default('/ebooks') }}
download_dir = {{ lazylibrarian_download_dir | default('/downloads/complete/lazylibrarian') }}
ebook_type = epub, mobi, pdf, azw3
audiobook_type = mp3, m4b, opus, ogg
[API]
api_enabled = True
api_key = {{ lazylibrarian_api_key }}
api_ro_key = {{ lazylibrarian_api_ro_key }}
book_api = GoodReads
ol_api = True
gb_api = {{ lazylibrarian_gb_api | default('') }}
gb_country = US
[WEBSERVER]
http_user = {{ lazylibrarian_http_user | default('lazylibrarianuser') }}
http_pass = {{ lazylibrarian_http_pass }}
[LOGGING]
logdir = {{ lazylibrarian_logdir | default('/config/Logs') }}
detaileduilog = True
[IMPORTER]
multi_source = True
[TELEMETRY]
server_id = {{ lazylibrarian_server_id | default('') }}
[SABNZBD]
sab_host = {{ lazylibrarian_sab_host | default('localhost') }}
sab_port = {{ lazylibrarian_sab_port | default(8082) }}
sab_user = {{ sabnzbd_username | default('sabnzbduser') }}
sab_pass = {{ sabnzbd_password }}
sab_api = {{ sabnzbd_api_key }}
sab_cat = {{ lazylibrarian_sab_cat | default('lazylibrarian') }}
[USENET]
nzb_downloader_sabnzbd = True
[TORRENT]
tor_downloader_qbittorrent = True
seed_wait = False
[QBITTORRENT]
qbittorrent_host = {{ lazylibrarian_qbit_host | default('localhost') }}
qbittorrent_port = {{ lazylibrarian_qbit_port | default(8082) }}
qbittorrent_user = {{ qbittorrent_username | default('qbittorrentuser') }}
qbittorrent_pass = {{ qbittorrent_password }}
qbittorrent_label = {{ lazylibrarian_qbit_label | default('lazylibrarian') }}
[ANNA]
anna_key = {{ lazylibrarian_anna_key }}
anna = True
[POSTPROCESS]
ebook_dest_folder = $Author/$Title $$
audiobook_dest_folder = $Author/$Title
del_downloadfailed = True
[MAGAZINES]
mag_tab = False
[PROWL]
prowl_priority = -2
[PREPROCESS]
ebook_wanted_formats = azw3, mobi, epub
{% for indexer in lazylibrarian_newznab_indexers | default([]) %}
[Newznab_{{ loop.index0 }}]
dispname = {{ indexer.name }}
enabled = {{ indexer.enabled | default(True) | string }}
host = {{ indexer.host }}
api = {{ indexer.api }}
generalsearch = search
bookcat = {{ indexer.bookcat | default('7020') }}
{% if indexer.magcat is defined %}
magcat = {{ indexer.magcat }}
{% endif %}
{% if indexer.comiccat is defined %}
comiccat = {{ indexer.comiccat }}
{% endif %}
updated = {{ indexer.updated | default('2026-01-27') }}
dlpriority = {{ indexer.dlpriority | default(26) }}
{% endfor %}
{% for indexer in lazylibrarian_torznab_indexers | default([]) %}
[Torznab_{{ loop.index0 }}]
dispname = {{ indexer.name }}
enabled = {{ indexer.enabled | default(True) | string }}
host = {{ indexer.host }}
api = {{ indexer.api }}
generalsearch = search
{% if indexer.bookcat is defined %}
bookcat = {{ indexer.bookcat }}
{% endif %}
{% if indexer.magcat is defined %}
magcat = {{ indexer.magcat }}
{% endif %}
{% if indexer.comiccat is defined %}
comiccat = {{ indexer.comiccat }}
{% endif %}
updated = {{ indexer.updated | default('2026-01-27') }}
dlpriority = {{ indexer.dlpriority | default(26) }}
seeders = {{ indexer.seeders | default(1) }}
{% if indexer.seed_ratio is defined %}
seed_ratio = {{ indexer.seed_ratio }}
{% endif %}
{% endfor %}
{% for indexer in lazylibrarian_generic_indexers | default([]) %}
[GEN_{{ loop.index0 }}]
dispname = {{ indexer.name }}
enabled = {{ indexer.enabled | default(True) | string }}
host = {{ indexer.host }}
search = {{ indexer.search }}
{% endfor %}

49
roles/quadlets/lidarr.yml Normal file
View File

@@ -0,0 +1,49 @@
quadlets:
- name: lidarr
type: build
image: localhost/lidarr:latest
pull: missing
format: oci
force_rm: true
container_file: |
FROM {{ alpine_base_image }}
LABEL maintainer="{{ maintainer }}"
RUN apk add --no-cache \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
icu-libs libintl sqlite-libs curl libmediainfo xmlstarlet jq \
&& url=$(curl -sL "https://api.github.com/repos/Lidarr/Lidarr/releases/latest" \
| jq -r '.assets[] | select(.name | test("linux-musl-core-x64.tar.gz$")) | .browser_download_url') \
&& [ -n "$url" ] \
&& curl -L -o /tmp/Lidarr.tar.gz "$url" \
&& mkdir -p /app && tar -xzf /tmp/Lidarr.tar.gz --strip-components=1 -C /app \
&& chmod +x /app/Lidarr \
&& rm /tmp/Lidarr.tar.gz
WORKDIR /app
- name: lidarr
type: container
image: localhost/lidarr:latest
volumes:
- "{{ config_root }}/lidarr:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
- "{{ media_root }}/music:/music:Z"
env:
TZ: "{{ tz }}"
LIDARR__APP__LAUNCHBROWSER: false
LIDARR__APP__INSTANCENAME: "{{ LIDARR__APP__INSTANCENAME | default('Lidarr') }}"
LIDARR__AUTH__METHOD: "{{ LIDARR__AUTH__METHOD | default('Forms') }}"
LIDARR__AUTH__APIKEY: "{{ LIDARR__AUTH__APIKEY | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits')) }}"
LIDARR__AUTH__ENABLED: "{{ LIDARR__AUTH__ENABLED | default(true) }}"
LIDARR__AUTH__REQUIRED: "{{ LIDARR__AUTH__REQUIRED | default('DisabledForLocalAddresses') }}"
LIDARR__SERVER__ENABLESSL: "{{ LIDARR__SERVER__ENABLESSL | default(false) }}"
LIDARR__SERVER__PORT: "{{ LIDARR__SERVER__PORT | default('8686') }}"
LIDARR__SERVER__SSLPORT: "{{ LIDARR__SERVER__SSLPORT | default('6868') }}"
LIDARR__SERVER__BINDADDRESS: "{{ LIDARR__SERVER__BINDADDRESS | default('*') }}"
LIDARR__SERVER__SSLCERTPATH: "{{ LIDARR__SERVER__SSLCERTPATH | default('/config/ssl/server.crt') }}"
LIDARR__SERVER__SSLCERTPASSWORD: "{{ LIDARR__SERVER__SSLCERTPASSWORD | default('/config/ssl/server.key') }}"
LIDARR__LOG__ANALYTICSENABLED: "{{ LIDARR__LOG__ANALYTICSENABLED | default(false) }}"
LIDARR__LOG__LEVEL: "{{ LIDARR__LOG__LEVEL | default('info') }}"
LIDARR__UPDATE__AUTOMATICALLY: "{{ LIDARR__UPDATE__AUTOMATICALLY | default(false) }}"
command: ["/app/Lidarr", "-nobrowser", "--data=/config"]
restart_policy: on-failure

View File

@@ -0,0 +1,47 @@
---
quadlets:
- name: prowlarr
type: build
image: localhost/prowlarr:latest
pull: missing
format: oci
force_rm: true
container_file: |
FROM {{ alpine_base_image }}
LABEL maintainer="{{ maintainer }}"
RUN apk add --no-cache \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
libintl icu-libs sqlite-libs curl jq \
&& url=$(curl -sL "https://api.github.com/repos/Prowlarr/Prowlarr/releases/latest" \
| jq -r '.assets[] | select(.name | test("linux-musl-core-x64.tar.gz$")) | .browser_download_url') \
&& [ -n "$url" ] \
&& curl -L -o /tmp/Prowlarr.tar.gz "$url" \
&& mkdir -p /app \
&& tar -xzf /tmp/Prowlarr.tar.gz -C /app --strip-components=1 \
&& rm /tmp/Prowlarr.tar.gz
WORKDIR /app
- name: prowlarr
type: container
image: localhost/prowlarr:latest
volumes:
- "{{ config_root }}/prowlarr:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
env:
TZ: "{{ tz }}"
PROWLARR__APP__LAUNCHBROWSER: false
PROWLARR__APP__INSTANCENAME: "{{ PROWLARR__APP__INSTANCENAME | default('Prowlarr') }}"
PROWLARR__AUTH__METHOD: "{{ PROWLARR__AUTH__METHOD | default('Forms') }}"
PROWLARR__AUTH__REQUIRED: "{{ PROWLARR__AUTH__REQUIRED | default('DisabledForLocalAddresses') }}"
PROWLARR__AUTH__APIKEY: "{{ PROWLARR__AUTH__APIKEY | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits')) }}"
PROWLARR__SERVER__ENABLESSL: "{{ PROWLARR__SERVER__ENABLESSL | default(false) }}"
PROWLARR__SERVER__SSLPORT: "{{ PROWLARR__SERVER__SSLPORT | default('6969') }}"
PROWLARR__SERVER__PORT: "{{ PROWLARR__SERVER__PORT | default('9696') }}"
PROWLARR__SERVER__BINDADDRESS: "{{ PROWLARR__SERVER__BINDADDRESS | default('*') }}"
PROWLARR__LOG__ANALYTICSENABLED: "{{ PROWLARR__LOG__ANALYTICSENABLED | default(false) }}"
PROWLARR__LOG__LEVEL: "{{ PROWLARR__LOG__LEVEL | default('info') }}"
PROWLARR__UPDATE__AUTOMATICALLY: "{{ PROWLARR__UPDATE__AUTOMATICALLY | default(false) }}"
command: ["/app/Prowlarr", "--nobrowser", "--data=/config"]
restart_policy: on-failure

View File

@@ -0,0 +1,162 @@
quadlets:
- name: qbittorrent
type: build
image: localhost/qbittorrent:latest
pull: missing
format: oci
force_rm: true
container_file: |
FROM {{ alpine_base_image }}
LABEL maintainer="{{ maintainer }}"
RUN apk add --no-cache \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
qbittorrent-nox \
&& mkdir -p /app \
&& ln -s /usr/bin/qbittorrent-nox /app/qbittorrent-nox
WORKDIR /app
- name: qbittorrent
type: container
image: localhost/qbittorrent:latest
volumes:
- "{{ config_root }}/qbittorrent:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
- "{{ monitor_root }}/torrents:/torrents:Z"
env:
TZ: "{{ tz }}"
command:
- "qbittorrent-nox"
- "--profile=/config"
restart_policy: on-failure
configs:
- path: "{{ config_root }}/qbittorrent/config/qBittorrent.conf"
template: |
[Application]
FileLogger\Age=1
FileLogger\AgeType=1
FileLogger\Backup=true
FileLogger\DeleteOld=true
FileLogger\Enabled=false
FileLogger\MaxSizeBytes=66560
FileLogger\Path=/config/qBittorrent/data/logs
MemoryWorkingSetLimit=2048
[AutoRun]
OnTorrentAdded\Enabled=false
OnTorrentAdded\Program=
enabled=false
program=
[BitTorrent]
MergeTrackersEnabled={{ qbittorrent_merge_trackers | default(false) | lower }}
Session\AddExtensionToIncompleteFiles=true
Session\AddTorrentStopped=false
Session\AddTrackersEnabled={{ qbittorrent_add_trackers | default(false) | lower }}
Session\AdditionalTrackers={{ qbittorrent_additional_trackers | default('') }}
Session\AlternativeGlobalDLSpeedLimit={{ qbittorrent_alt_dl_limit | default(0) }}
Session\AlternativeGlobalUPSpeedLimit={{ qbittorrent_alt_up_limit | default(0) }}
Session\AnonymousModeEnabled={{ qbittorrent_anonymous_mode | default(true) | lower }}
Session\DefaultSavePath={{ qbittorrent_complete_dir | default('/downloads/complete') }}
Session\DisableAutoTMMByDefault=false
Session\DisableAutoTMMTriggers\CategorySavePathChanged=false
Session\DisableAutoTMMTriggers\DefaultSavePathChanged=false
Session\ExcludedFileNames=
Session\GlobalDLSpeedLimit={{ qbittorrent_dl_limit | default(0) }}
Session\GlobalMaxRatio={{ qbittorrent_max_ratio | default(1) }}
Session\GlobalMaxSeedingMinutes={{ qbittorrent_max_seed_minutes | default(0) }}
Session\GlobalUPSpeedLimit={{ qbittorrent_ul_limit | default(300) }}
Session\IgnoreLimitsOnLAN=true
Session\IgnoreSlowTorrentsForQueueing=true
Session\MaxActiveDownloads={{ qbittorrent_max_active_downloads | default(0) }}
Session\MaxActiveTorrents={{ qbittorrent_max_active_torrents | default(0) }}
Session\Port={{ qbittorrent_session_port | default(48442) }}
Session\QueueingSystemEnabled={{ qbittorrent_queueing_enabled | default(false) | lower }}
Session\SSL\Port={{ qbittorrent_ssl_port | default('11192') }}
Session\ShareLimitAction={{ qbittorrent_share_limit_action | default('Stop') }}
Session\TempPath={{ qbittorrent_temp_dir | default('/downloads/incomplete/qbittorrent') }}
Session\TempPathEnabled=true
Session\UseAlternativeGlobalSpeedLimit=true
[Core]
AutoDeleteAddedTorrentFile=IfAdded
[LegalNotice]
Accepted=true
[Meta]
MigrationVersion=8
[Network]
Cookies=@Invalid()
Proxy\HostnameLookupEnabled=false
Proxy\Profiles\BitTorrent=true
Proxy\Profiles\Misc=true
Proxy\Profiles\RSS=true
[Preferences]
Advanced\AnonymousMode={{ qbittorrent_anonymous_mode | default(true) | lower }}
Advanced\RecheckOnCompletion={{ qbittorrent_recheck_on_completion | default(false) | lower }}
Advanced\trackerPort={{ qbittorrent_tracker_port | default(9000) }}
Advanced\trackerPortForwarding=false
Bittorrent\MaxRatio={{ qbittorrent_max_ratio | default(1) }}
Connection\GlobalDLLimitAlt={{ qbittorrent_global_dl_limit_alt | default(0) }}
Connection\GlobalUPLimitAlt={{ qbittorrent_global_up_limit_alt | default(0) }}
Connection\PortRangeMin={{ qbittorrent_port_range_min | default(48442) }}
Connection\ResolvePeerCountries=true
Downloads\SavePath={{ qbittorrent_complete_dir | default('/downloads/complete') }}
Downloads\ScanDirsLastPath={{ qbittorrent_scan_dir | default('/torrents') }}
Downloads\ScanDirsV2=@Variant(\0\0\0\x1c\0\0\0\0)
Downloads\TempPath={{ qbittorrent_temp_dir | default('/downloads/incomplete/qbittorrent') }}
Downloads\TempPathEnabled=true
Downloads\UseIncompleteExtension=true
DynDNS\DomainName=changeme.dyndns.org
DynDNS\Enabled=false
DynDNS\Password=
DynDNS\Service=DynDNS
DynDNS\Username=
General\Locale={{ qbittorrent_locale | default('en') }}
MailNotification\email=
MailNotification\enabled=false
MailNotification\password=
MailNotification\req_auth=true
MailNotification\req_ssl=false
MailNotification\sender=qBittorrent_notification@example.com
MailNotification\smtp_server=smtp.changeme.com
MailNotification\username=
Queueing\IgnoreSlowTorrents=true
Queueing\MaxActiveDownloads={{ qbittorrent_max_active_downloads | default(20) }}
Queueing\MaxActiveTorrents={{ qbittorrent_max_active_torrents | default(20) }}
Queueing\QueueingEnabled=true
WebUI\Address={{ qbittorrent_host | default('') }}
WebUI\AlternativeUIEnabled=false
WebUI\AuthSubnetWhitelist={{ qbittorrent_subnet_whitelist | default('') }}
WebUI\AuthSubnetWhitelistEnabled=true
WebUI\BanDuration=3600
WebUI\CSRFProtection=true
WebUI\ClickjackingProtection=true
WebUI\CustomHTTPHeaders=
WebUI\CustomHTTPHeadersEnabled=false
WebUI\Enabled=true
WebUI\HTTPS\CertificatePath={{ qbittorrent_https_cert | default('/config/qBittorrent/config/ssl/server.crt') }}
WebUI\HTTPS\Enabled={{ qbittorrent_enable_https | default(false) | lower }}
WebUI\HTTPS\KeyPath={{ qbittorrent_https_key | default('/config/qBittorrent/config/ssl/server.key') }}
WebUI\HostHeaderValidation=false
WebUI\LocalHostAuth=false
WebUI\MaxAuthenticationFailCount=5
WebUI\Password_PBKDF2="{{ qbittorrent_password_pbkdf2 | default('') }}"
WebUI\Password_ha1={{ qbittorrent_password_ha1 | default('') }}
WebUI\Port={{ qbittorrent_ui_port | default(8082) }}
WebUI\ReverseProxySupportEnabled=false
WebUI\RootFolder=
WebUI\SecureCookie=true
WebUI\ServerDomains={{ qbittorrent_server_domains | default('*') }}
WebUI\SessionTimeout=3600
WebUI\TrustedReverseProxiesList=
WebUI\UseUPnP=true
WebUI\Username={{ qbittorrent_username | default('qbittorrentuser') }}
[RSS]
AutoDownloader\DownloadRepacks={{ qbittorrent_download_repacks | default(true) | lower }}
AutoDownloader\SmartEpisodeFilter=s(\\d+)e(\\d+), (\\d+)x(\\d+), "(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", "(\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4})"

46
roles/quadlets/radarr.yml Normal file
View File

@@ -0,0 +1,46 @@
quadlets:
- name: radarr
type: build
image: localhost/radarr:latest
pull: missing
format: oci
force_rm: true
container_file: |
FROM {{ alpine_base_image }}
LABEL maintainer="{{ maintainer }}"
RUN apk add --no-cache \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
icu-libs libintl sqlite-libs curl libmediainfo xmlstarlet jq \
&& url=$(curl -sL "https://api.github.com/repos/Radarr/Radarr/releases/latest" \
| jq -r '.assets[] | select(.name | test("linux-musl-core-x64.tar.gz$")) | .browser_download_url') \
&& curl -L -o /tmp/radarr.tar.gz "$url" \
&& mkdir -p /app \
&& tar -xzf /tmp/radarr.tar.gz --strip-components=1 -C /app \
&& chmod +x /app/Radarr \
&& rm -rf /app/Radarr.Update
WORKDIR /app
- name: radarr
type: container
image: localhost/radarr:latest
volumes:
- "{{ config_root }}/radarr:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
- "{{ media_root }}/movies:/movies:Z"
env:
TZ: "{{ tz }}"
RADARR__APP__LAUNCHBROWSER: false
RADARR__APP__INSTANCENAME: "{{ RADARR__APP__INSTANCENAME | default('Radarr') }}"
RADARR__AUTH__METHOD: "{{ RADARR__AUTH__METHOD | default('Forms') }}"
RADARR__AUTH__APIKEY: "{{ RADARR__AUTH__APIKEY | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits')) }}"
RADARR__AUTH__ENABLED: "{{ RADARR__AUTH__ENABLED | default(true) }}"
RADARR__AUTH__REQUIRED: "{{ RADARR__AUTH__REQUIRED | default('DisabledForLocalAddresses') }}"
RADARR__SERVER__ENABLESSL: "{{ RADARR__SERVER__ENABLESSL | default(false) }}"
RADARR__SERVER__PORT: "{{ RADARR__SERVER__PORT | default('7878') }}"
RADARR__SERVER__BINDADDRESS: "{{ RADARR__SERVER__BINDADDRESS | default('*') }}"
RADARR__LOG__ANALYTICSENABLED: "{{ RADARR__LOG__ANALYTICSENABLED | default(false) }}"
RADARR__LOG__LEVEL: "{{ RADARR__LOG__LEVEL | default('info') }}"
RADARR__UPDATE__AUTOMATICALLY: "{{ RADARR__UPDATE__AUTOMATICALLY | default(false) }}"
command: ["/app/Radarr", "--nobrowser", "--data=/config"]
restart_policy: on-failure

524
roles/quadlets/sabnzbd.yml Normal file
View File

@@ -0,0 +1,524 @@
quadlets:
- name: sabnzbd
type: build
image: localhost/sabnzbd:latest
pull: missing
format: oci
force_rm: true
container_file: |
FROM {{ alpine_base_image }}
LABEL maintainer="{{ maintainer }}"
RUN \
rm -rf /etc/apk/repositories \
&& apk add --no-cache \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
python3 py3-pip git unzip ca-certificates par2cmdline 7zip \
&& apk add --no-cache --virtual=.build-deps \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
autoconf automake build-base openssl-dev libffi-dev python3-dev \
&& apk add --no-cache -X '{{ alpine_mirror }}/v3.14/main' unrar \
&& git clone --depth 1 --branch master https://github.com/sabnzbd/sabnzbd /app \
&& python3 -m venv /venv \
&& . /venv/bin/activate \
&& pip install --no-cache-dir -r /app/requirements.txt \
&& apk del --purge .build-deps
- name: sabnzbd
type: container
image: localhost/sabnzbd:latest
volumes:
- "{{ config_root }}/sabnzbd:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
- "{{ monitor_root }}/nzbs:/nzbs:Z"
env:
TZ: "{{ tz }}"
command:
- "/venv/bin/python"
- "-OO"
- "/app/SABnzbd.py"
- "--config-file=/config/sabnzbd.ini"
- "--browser=0"
restart_policy: on-failure
configs:
- path: "{{ config_root }}/sabnzbd/sabnzbd.ini"
template: |
__version__ = 19
__encoding__ = utf-8
[misc]
username = {{ sabnzbd_username | default("admin") }}
password = {{ sabnzbd_password | default("admin") }}
host = {{ sabnzbd_host | default("::") }}
port = {{ sabnzbd_http_port | default("8081") }}
api_key = {{ sabnzbd_api_key }}
nzb_key = {{ sabnzbd_nzb_key }}
download_dir = {{ sabnzbd_download_dir | default("/downloads/incomplete/sabnzbd") }}
complete_dir = {{ sabnzbd_complete_dir | default("/downloads/complete") }}
dirscan_dir = {{ sabnzbd_dirscan_dir | default("/nzbs") }}
web_dir = Glitter
web_color = Night
inet_exposure = 4
cleanup_list = sfv, m3u, nfo
unwanted_extensions = exe, com
ignore_samples = 1
enable_all_par = 1
new_nzb_on_failure = 1
replace_dots = 1
prospective_par_download = 1
deobfuscate_final_filenames = 1
direct_unpack = 1
pause_on_pwrar = 2
propagation_delay = 0
top_only = 1
pre_script = None
queue_complete = ""
queue_complete_pers = 0
bandwidth_perc = 100
refresh_rate = 1
interface_settings = '{"dateFormat":"fromNow","extraQueueColumns":["category","age"],"extraHistoryColumns":["size"],"displayCompact":false,"displayFullWidth":false,"confirmDeleteQueue":true,"confirmDeleteHistory":true,"keyboardShortcuts":true}'
queue_limit = 30
config_lock = 0
sched_converted = 0
notified_new_skin = 2
direct_unpack_tested = 1
check_new_rel = 1
auto_browser = 0
language = en
enable_https_verification = 1
https_port = {{ sabnzbd_https_port | default("9090") }}
bandwidth_max = 60M
cache_limit = 1G
https_cert = {{ sabnzbd_https_cert | default("/config/admin/server.cert") }}
https_key = {{ sabnzbd_https_key | default("/config/admin/server.key") }}
https_chain = ""
enable_https = {{ sabnzbd_enable_https | default(false) | int }}
permissions = ""
download_free = ""
complete_free = ""
fulldisk_autoresume = 0
script_dir = ""
nzb_backup_dir = ""
admin_dir = admin
dirscan_speed = 5
password_file = ""
log_dir = logs
max_art_tries = 3
load_balancing = 2
sfv_check = 1
quick_check_ext_ignore = nfo, sfv, srr
script_can_fail = 0
enable_recursive = 1
flat_unpack = 0
par_option = ""
pre_check = 0
nice = ""
win_process_prio = 3
ionice = ""
fail_hopeless_jobs = 1
fast_fail = 1
auto_disconnect = 1
no_dupes = 0
no_series_dupes = 0
series_propercheck = 1
auto_sort = ""
direct_unpack_threads = 3
folder_rename = 1
replace_spaces = 0
safe_postproc = 1
pause_on_post_processing = 0
sanitize_safe = 0
action_on_unwanted_extensions = 0
unwanted_extensions_mode = 0
history_retention = ""
enable_meta = 1
quota_size = ""
quota_day = ""
quota_resume = 0
quota_period = m
rating_enable = 0
rating_host = ""
rating_api_key = ""
rating_filter_enable = 0
rating_filter_abort_audio = 0
rating_filter_abort_video = 0
rating_filter_abort_encrypted = 0
rating_filter_abort_encrypted_confirm = 0
rating_filter_abort_spam = 0
rating_filter_abort_spam_confirm = 0
rating_filter_abort_downvoted = 0
rating_filter_abort_keywords = ""
rating_filter_pause_audio = 0
rating_filter_pause_video = 0
rating_filter_pause_encrypted = 0
rating_filter_pause_encrypted_confirm = 0
rating_filter_pause_spam = 0
rating_filter_pause_spam_confirm = 0
rating_filter_pause_downvoted = 0
rating_filter_pause_keywords = ""
enable_tv_sorting = 0
tv_sort_string = ""
tv_categories = tv,
enable_movie_sorting = 0
movie_sort_string = ""
movie_sort_extra = -cd%1
movie_extra_folder = 0
movie_categories = movies,
enable_date_sorting = 0
date_sort_string = ""
date_categories = tv,
schedlines = ,
rss_rate = 60
ampm = 0
replace_illegal = 1
start_paused = 0
preserve_paused_state = 0
enable_par_cleanup = 1
process_unpacked_par2 = 1
enable_unrar = 1
enable_unzip = 1
enable_7zip = 1
enable_filejoin = 1
enable_tsjoin = 1
overwrite_files = 0
ignore_unrar_dates = 0
backup_for_duplicates = 1
empty_postproc = 0
wait_for_dfolder = 0
rss_filenames = 0
api_logging = 1
html_login = 1
osx_menu = 1
osx_speed = 1
warn_dupl_jobs = 1
helpful_warnings = 1
keep_awake = 1
win_menu = 1
allow_incomplete_nzb = 0
enable_broadcast = 1
ipv6_hosting = 0
fixed_ports = 1
api_warnings = 1
disable_api_key = 0
no_penalties = 0
x_frame_options = 1
allow_old_ssl_tls = 0
num_decoders = 3
rss_odd_titles = nzbindex.nl/, nzbindex.com/, nzbclub.com/
req_completion_rate = 100.2
selftest_host = self-test.sabnzbd.org
movie_rename_limit = 100M
episode_rename_limit = 20M
size_limit = 0
show_sysload = 2
history_limit = 20
wait_ext_drive = 5
max_foldername_length = 246
nomedia_marker = ""
ipv6_servers = 1
url_base = /sabnzbd
host_whitelist = htpc,
local_ranges = ,
max_url_retries = 10
downloader_sleep_time = 10
ssdp_broadcast_interval = 15
socks5_proxy_url = ""
email_server = ""
email_to = ,
email_from = ""
email_account = ""
email_pwd = ""
email_endjob = 0
email_full = 0
email_dir = ""
email_rss = 0
email_cats = *,
num_simd_decoders = 2
ext_rename_ignore = ,
sorters_converted = 1
backup_dir = ""
replace_underscores = 0
tray_icon = 1
enable_season_sorting = 1
receive_threads = 2
switchinterval = 0.005
end_queue_script = None
no_smart_dupes = 0
dupes_propercheck = 1
history_retention_option = all
history_retention_number = 1
enable_multipar = 1
disable_archive = 0
ipv6_staging = 0
verify_xff_header = 0
config_conversion_version = 4
disable_par2cmdline = 0
unrar_parameters = ""
outgoing_nntp_ip = ""
[categories]
[[*]]
name = *
order = 0
pp = 3
script = None
dir = ""
newzbin = ""
priority = 0
[[music]]
name = music
order = 0
pp = ""
script = Default
dir = music
newzbin = music
priority = -100
[[tv]]
name = tv
order = 0
pp = ""
script = Default
dir = tv
newzbin = tv
priority = -100
[[movies]]
name = movies
order = 0
pp = ""
script = Default
dir = movies
newzbin = "movies, movie"
priority = -100
[[radarr]]
name = radarr
order = 0
pp = ""
script = Default
dir = radarr
newzbin = "movies, movie"
priority = -100
[[sonarr]]
name = sonarr
order = 0
pp = ""
script = Default
dir = sonarr
newzbin = tv
priority = -100
[[other]]
name = other
order = 1
pp = ""
script = Default
dir = other
newzbin = "games, game, other"
priority = -100
[[lidarr]]
name = lidarr
order = 2
pp = ""
script = Default
dir = lidarr
newzbin = music
priority = -100
[[readarr]]
name = readarr
order = 3
pp = ""
script = Default
dir = readarr
newzbin = ebooks
priority = -100
[[lazylibrarian]]
name = lazylibrarian
order = 4
pp = ""
script = Default
dir = lazylibrarian
newzbin = "ebooks, audiobooks"
priority = -100
[servers]
{% for s in sabnzbd_servers | default([]) %}
[[{{ s.name }}]]
name = {{ s.name }}
displayname = {{ s.displayname | default(s.name) }}
host = {{ s.host | default(s.name) }}
port = {{ s.port | default(563) }}
timeout = {{ s.timeout | default(60) }}
username = {{ s.username| default('') }}
password = {{ s.password| default('') }}
connections = {{ s.connections | default(50) }}
ssl = {{ s.ssl | default(1) }}
ssl_verify = {{ s.ssl_verify | default(1) }}
ssl_ciphers = {{ s.ssl_ciphers | default('""') }}
enable = {{ s.enable| default(1) }}
required = {{ s.required| default(0) }}
optional = {{ s.optional| default(0) }}
retention = {{ s.retention | default(0) }}
expire_date = {{ s.expire_date | default('""') }}
quota = {{ s.quota | default('""') }}
usage_at_start = {{ s.usage_at_start | default(0) }}
priority = {{ s.priority| default(0) }}
notes = {{ s.notes | default('""') }}
{% endfor %}
[logging]
log_level = 1
max_log_size = 5242880
log_backups = 5
[ncenter]
ncenter_enable = 0
ncenter_cats = *,
ncenter_prio_startup = 1
ncenter_prio_download = 0
ncenter_prio_pause_resume = 0
ncenter_prio_pp = 0
ncenter_prio_complete = 1
ncenter_prio_failed = 1
ncenter_prio_disk_full = 1
ncenter_prio_new_login = 0
ncenter_prio_warning = 0
ncenter_prio_error = 0
ncenter_prio_queue_done = 1
ncenter_prio_other = 1
ncenter_prio_quota = 1
[acenter]
acenter_enable = 0
acenter_cats = *,
acenter_prio_startup = 0
acenter_prio_download = 0
acenter_prio_pause_resume = 0
acenter_prio_pp = 0
acenter_prio_complete = 1
acenter_prio_failed = 1
acenter_prio_disk_full = 1
acenter_prio_new_login = 0
acenter_prio_warning = 0
acenter_prio_error = 0
acenter_prio_queue_done = 1
acenter_prio_other = 1
acenter_prio_quota = 1
[ntfosd]
ntfosd_enable = 1
ntfosd_cats = *,
ntfosd_prio_startup = 1
ntfosd_prio_download = 0
ntfosd_prio_pause_resume = 0
ntfosd_prio_pp = 0
ntfosd_prio_complete = 1
ntfosd_prio_failed = 1
ntfosd_prio_disk_full = 1
ntfosd_prio_new_login = 0
ntfosd_prio_warning = 0
ntfosd_prio_error = 0
ntfosd_prio_queue_done = 1
ntfosd_prio_other = 1
ntfosd_prio_quota = 1
[prowl]
prowl_enable = 0
prowl_cats = *,
prowl_apikey = ""
prowl_prio_startup = -3
prowl_prio_download = -3
prowl_prio_pause_resume = -3
prowl_prio_pp = -3
prowl_prio_complete = 0
prowl_prio_failed = 1
prowl_prio_disk_full = 1
prowl_prio_new_login = -3
prowl_prio_warning = -3
prowl_prio_error = -3
prowl_prio_queue_done = 0
prowl_prio_other = 0
prowl_prio_quota = 0
[pushover]
pushover_token = ""
pushover_userkey = ""
pushover_device = ""
pushover_emergency_expire = 3600
pushover_emergency_retry = 60
pushover_enable = 0
pushover_cats = *,
pushover_prio_startup = -3
pushover_prio_download = -2
pushover_prio_pause_resume = -2
pushover_prio_pp = -3
pushover_prio_complete = -1
pushover_prio_failed = -1
pushover_prio_disk_full = 1
pushover_prio_new_login = -3
pushover_prio_warning = 1
pushover_prio_error = 1
pushover_prio_queue_done = -1
pushover_prio_other = -1
pushover_prio_quota = -1
[pushbullet]
pushbullet_enable = 0
pushbullet_cats = *,
pushbullet_apikey = ""
pushbullet_device = ""
pushbullet_prio_startup = 0
pushbullet_prio_download = 0
pushbullet_prio_pause_resume = 0
pushbullet_prio_pp = 0
pushbullet_prio_complete = 1
pushbullet_prio_failed = 1
pushbullet_prio_disk_full = 1
pushbullet_prio_new_login = 0
pushbullet_prio_warning = 0
pushbullet_prio_error = 0
pushbullet_prio_queue_done = 0
pushbullet_prio_other = 1
pushbullet_prio_quota = 1
[nscript]
nscript_enable = 0
nscript_cats = *,
nscript_script = ""
nscript_parameters = ""
nscript_prio_startup = 1
nscript_prio_download = 0
nscript_prio_pause_resume = 0
nscript_prio_pp = 0
nscript_prio_complete = 1
nscript_prio_failed = 1
nscript_prio_disk_full = 1
nscript_prio_new_login = 0
nscript_prio_warning = 0
nscript_prio_error = 0
nscript_prio_queue_done = 1
nscript_prio_other = 1
nscript_prio_quota = 1
[apprise]
apprise_enable = 0
apprise_cats = *,
apprise_urls = ""
apprise_target_startup = ""
apprise_target_startup_enable = 0
apprise_target_download = ""
apprise_target_download_enable = 0
apprise_target_pause_resume = ""
apprise_target_pause_resume_enable = 0
apprise_target_pp = ""
apprise_target_pp_enable = 0
apprise_target_complete = ""
apprise_target_complete_enable = 1
apprise_target_failed = ""
apprise_target_failed_enable = 1
apprise_target_disk_full = ""
apprise_target_disk_full_enable = 0
apprise_target_new_login = ""
apprise_target_new_login_enable = 1
apprise_target_warning = ""
apprise_target_warning_enable = 0
apprise_target_error = ""
apprise_target_error_enable = 0
apprise_target_queue_done = ""
apprise_target_queue_done_enable = 0
apprise_target_other = ""
apprise_target_other_enable = 1
apprise_target_quota = ""
apprise_target_quota_enable = 1

44
roles/quadlets/sonarr.yml Normal file
View File

@@ -0,0 +1,44 @@
quadlets:
- name: sonarr
type: build
image: localhost/sonarr:latest
pull: missing
format: oci
force_rm: true
container_file: |
FROM {{ alpine_base_image }}
LABEL maintainer="{{ maintainer }}"
RUN apk add --no-cache \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
libintl sqlite-libs icu-libs curl jq \
&& url=$(curl -sL "https://api.github.com/repos/Sonarr/Sonarr/releases/latest" \
| jq -r '.assets[] | select(.name | test("linux-musl-x64.tar.gz$")) | .browser_download_url') \
&& curl -L -o /tmp/Sonarr.tar.gz "$url" \
&& mkdir -p /app && tar -xzf /tmp/Sonarr.tar.gz -C /app --strip-components=1
WORKDIR /app
- name: sonarr
type: container
image: localhost/sonarr:latest
volumes:
- "{{ config_root }}/sonarr:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
- "{{ media_root }}/tv:/tv:Z"
env:
TZ: "{{ tz }}"
SONARR__APP__LAUNCHBROWSER: false
SONARR__APP__INSTANCENAME: "{{ SONARR__APP__INSTANCENAME | default('Sonarr') }}"
SONARR__AUTH__METHOD: "{{ SONARR__AUTH__METHOD | default('Forms') }}"
SONARR__AUTH__APIKEY: "{{ SONARR__AUTH__APIKEY | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits')) }}"
SONARR__SERVER__ENABLESSL: "{{ SONARR__SERVER__ENABLESSL | default(false) }}"
SONARR__SERVER__SSLPORT: "{{ SONARR__SERVER__SSLPORT | default('9898') }}"
SONARR__SERVER__PORT: "{{ SONARR__SERVER__PORT | default('8989') }}"
SONARR__SERVER__BINDADDRESS: "{{ SONARR__SERVER__BINDADDRESS | default('*') }}"
SONARR__SERVER__SSLCERTPATH: "{{ SONARR__SERVER__SSLCERTPATH | default('/config/ssl/server.crt') }}"
SONARR__SERVER__SSLCERTPASSWORD: "{{ SONARR__SERVER__SSLCERTPASSWORD | default('/config/ssl/server.key') }}"
SONARR__LOG__ANALYTICSENABLED: "{{ SONARR__LOG__ANALYTICSENABLED | default(false) }}"
SONARR__UPDATE__AUTOMATICALLY: "{{ SONARR__UPDATE__AUTOMATICALLY | default(false) }}"
SONARR__LOG__LEVEL: "{{ SONARR__LOG__LEVEL | default('info') }}"
command: ["/app/Sonarr", "-data=/config"]
restart_policy: on-failure

View File

@@ -0,0 +1,49 @@
---
- name: Load all quadlet app definitions
ansible.builtin.set_fact:
quadlets_apps: "{{ quadlets_apps | default([]) + [lookup('file', item.path) | from_yaml] }}"
loop: "{{ lookup('ansible.builtin.find', role_path, patterns='*.yml', excludes=['tasks']).files }}"
- name: Deploy app configs
ansible.builtin.template:
content: "{{ item.1.template }}"
dest: "{{ item.1.path }}"
mode: '0644'
loop: "{{ quadlets_apps | subelements('configs', skip_missing=True) }}"
when: item.1.template is defined
- name: Initialize quadlet specs
ansible.builtin.set_fact:
quadlets_all_specs: []
- name: Extract quadlet specs from app definitions
ansible.builtin.set_fact:
quadlets_all_specs: "{{ quadlets_all_specs + item.1 }}"
loop: "{{ quadlets_apps | subelements('quadlets', skip_missing=True) }}"
- name: Build final quadlet specs with pod injection
ansible.builtin.set_fact:
quadlets_specs: "{{ (pods | default([])) | map('combine', {'type': 'pod'}) | list }}"
- name: Add containers to quadlet specs with pod injection
ansible.builtin.set_fact:
quadlets_specs: "{{ quadlets_specs + [container_spec[0] | combine({'pod': item.0.name})] }}"
loop: "{{ pods | default([]) | subelements('containers') }}"
vars:
container_spec: "{{ quadlets_all_specs | selectattr('name', 'equalto', item.1) | selectattr('type', 'equalto', 'container') | list }}"
when: container_spec | length > 0
- name: Add build specs for enabled containers
ansible.builtin.set_fact:
quadlets_specs: "{{ quadlets_specs + [item] }}"
loop: "{{ quadlets_all_specs }}"
when:
- item.type == 'build'
- item.name in (pods | default([]) | map(attribute='containers') | flatten)
- name: Deploy quadlets using fedora.linux_system_roles.podman
ansible.builtin.include_role:
name: fedora.linux_system_roles.podman
vars:
podman_quadlet_specs: "{{ quadlets_specs }}"
when: quadlets_specs is defined and quadlets_specs | length > 0

View File

@@ -0,0 +1,73 @@
quadlets:
- name: traefik
type: container
image: docker.io/traefik:latest
pull: newer
volumes:
- "{{ config_root }}/traefik:/etc/traefik:Z"
env:
TZ: "{{ tz }}"
command:
- "--api.dashboard=true"
- "--api.insecure=true"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls.certFile=/etc/traefik/server.crt"
- "--entrypoints.websecure.http.tls.keyFile=/etc/traefik/server.key"
- "--log.level=INFO"
restart_policy: on-failure
configs:
- path: "{{ config_root }}/traefik/server.crt"
template: {{ traefik_server_cert }}
- path: "{{ config_root }}/traefik/server.key"
template: {{ traefik_server_key }}
- path: "{{ config_root }}/traefik/dynamic/dynamic.yml"
template: |
---
http:
middlewares:
redirect-https:
redirectScheme:
scheme: https
permanent: true
routers:
redirect-to-https:
rule: "HostRegexp(`{any:.*}`)"
entryPoints:
- web
middlewares:
- redirect-https
service: noop
{% for app_name in traefik_enabled_apps %}
{{ app_name }}:
rule: "PathPrefix(`/{{ app_name }}`)"
service: {{ app_name }}
entryPoints:
- websecure
tls: {}
{% endfor %}
dashboard:
rule: "PathPrefix(`/dashboard`)"
service: api@internal
entryPoints:
- websecure
tls: {}
services:
noop:
loadBalancer:
servers:
- url: "http://localhost"
{% for app_name in traefik_enabled_apps %}
{{ app_name }}:
loadBalancer:
servers:
- url: "http://localhost:{{ lookup('vars', app_name).port }}"
{% endfor %}

View File

@@ -0,0 +1,37 @@
quadlets:
- name: unpackerr
type: build
image: localhost/unpackerr:latest
pull: missing
format: oci
force_rm: true
container_file: |
FROM {{ alpine_base_image }}
LABEL maintainer="{{ maintainer }}"
RUN apk add --no-cache \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/main" \
-X "{{ alpine_mirror }}/v{{ alpine_base_image.split(':')[-1] }}/community" \
curl tar jq \
&& url=$(curl -sL "https://api.github.com/repos/davidnewhall/Unpackerr/releases/latest" \
| jq -r '.assets[] | select(.name == "unpackerr.amd64.linux.gz") | .browser_download_url') \
&& [ -n "$url" ] \
&& curl -L -o /tmp/unpackerr.gz "$url" \
&& mkdir -p /app \
&& gunzip -c /tmp/unpackerr.gz > /app/unpackerr \
&& chmod +x /app/unpackerr \
&& rm /tmp/unpackerr.gz
WORKDIR /app
- name: unpackerr
type: container
image: localhost/unpackerr:latest
volumes:
- "{{ config_root }}/unpackerr:/config:Z"
- "{{ download_root }}/htpc:/downloads:z"
env:
TZ: "{{ tz }}"
UNPACKERR__LOG__ANALYTICSENABLED: "{{ UNPACKERR__LOG__ANALYTICSENABLED | default(false) }}"
UNPACKERR__UPDATE__AUTOMATICALLY: "{{ UNPACKERR__UPDATE__AUTOMATICALLY | default(false) }}"
UNPACKERR__LOG__LEVEL: "{{ UNPACKERR__LOG__LEVEL | default('info') }}"
command: ["/app/unpackerr"]
restart_policy: on-failure

View File

@@ -1,25 +1,30 @@
--- ---
- name: Glob all files in role directory
ansible.builtin.find:
paths: "{{ role_path }}"
depth: 1
file_type: file
register: scripts_files
- name: Copy repo scripts to local bin (for remote hosts) - name: "Ensure script directory exist"
ansible.builtin.copy:
src: "{{ item }}"
dest: "{{ local_bin_dir | default(ansible_facts['env']['HOME'] ~ '/.local/bin') }}/{{ item | basename }}"
mode: "0755"
owner: "{{ local_bin_owner | default(ansible_facts['user_id']) }}"
group: "{{ local_bin_group | default(ansible_facts['user_gid']) }}"
with_fileglob:
- "{{ scripts_src_glob | default(playbook_dir + '/scripts/*') }}"
when: ansible_connection not in ['local', 'localhost'] and item is file
- name: Symlink repo scripts into local bin (stow-like, for local hosts)
ansible.builtin.file: ansible.builtin.file:
src: "{{ item }}" path: "{{ ansible_facts.env.HOME }}/.local/bin"
dest: "{{ local_bin_dir | default(ansible_facts['env']['HOME'] ~ '/.local/bin') }}/{{ item | basename }}" state: directory
mode: "0755"
- name: "Deploy scripts (local with symlinks)"
ansible.builtin.file:
src: "{{ item.path }}"
dest: "{{ ansible_facts.env.HOME }}/.local/bin/{{ item.path | basename }}"
state: link state: link
force: true force: true
owner: "{{ local_bin_owner | default(ansible_facts['user_id']) }}" loop: "{{ scripts_files.files }}"
group: "{{ local_bin_group | default(ansible_facts['user_gid']) }}" when: ansible_connection == 'local'
follow: false
with_fileglob: - name: "Deploy scripts (remote with copy)"
- "{{ scripts_src_glob | default(playbook_dir + '/scripts/*') }}" ansible.builtin.copy:
when: ansible_connection in ['local', 'localhost'] and item is file src: "{{ item.path }}"
dest: "{{ ansible_facts.env.HOME }}/.local/bin/{{ item.path | basename }}"
mode: '0755'
loop: "{{ scripts_files.files }}"
when: ansible_connection != 'local'

View File

@@ -1,11 +1,20 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Open a tiled tmux window with one pane per host each in its own tmux session. # Open a tiled tmux window with one pane per host each in its own tmux session.
# The local session is always the last (active) pane. # The local session is always the last (active) pane.
set -euo pipefail set -euo pipefail
# Accept hostnames from command-line arguments
if (( $# > 0 )); then
HOSTS=("$@")
else
# Default hostnames in the desired pane order
HOSTS=(
workstation
laptop
)
fi
# Configuration (override with env vars if desired) # Configuration (override with env vars if desired)
HOSTS=(workstation laptop) # hosts in pane order
REMOTE_SESSION=${REMOTE_SESSION:-main} # tmux session on remotes REMOTE_SESSION=${REMOTE_SESSION:-main} # tmux session on remotes
SYNCHRONIZE=${SYNCHRONIZE:-1} # 1 = broadcast keystrokes SYNCHRONIZE=${SYNCHRONIZE:-1} # 1 = broadcast keystrokes
INCLUDE_LOCAL=${INCLUDE_LOCAL:-1} # 0 = skip local host INCLUDE_LOCAL=${INCLUDE_LOCAL:-1} # 0 = skip local host
@@ -56,7 +65,8 @@ open_cmd() {
printf 'tmux -L %q new -A -s %q' "${SESSION}_local" "$REMOTE_SESSION" printf 'tmux -L %q new -A -s %q' "${SESSION}_local" "$REMOTE_SESSION"
fi fi
else else
printf 'ssh -t %q tmux new -A -s %q' "$tgt" "$REMOTE_SESSION" # SSH with keepalive settings for faster broken pipe detection
printf 'ssh -o ServerAliveInterval=20 -o ServerAliveCountMax=3 -t %q tmux new -A -s %q' "$tgt" "$REMOTE_SESSION"
fi fi
} }
@@ -74,8 +84,8 @@ tmux select-layout -t "$SESSION:0" tiled
# Keep panes visible when commands exit # Keep panes visible when commands exit
tmux set-option -t "$SESSION:0" remain-on-exit on tmux set-option -t "$SESSION:0" remain-on-exit on
# Auto-respawn any pane whose command dies # Reconnect dead panes when user focuses on them
tmux set-hook -t "$SESSION" pane-died "run-shell 'tmux respawn-pane -k -t #{pane_id}'" tmux set-hook -t "$SESSION" pane-focus-in "if -F '#{pane_dead}' 'respawn-pane -k -t #{pane_id}'"
# Activate the last pane (local host) # Activate the last pane (local host)
local_index=$(( ${#TARGETS[@]} - 1 )) local_index=$(( ${#TARGETS[@]} - 1 ))

View File

@@ -1,77 +0,0 @@
---
- name: Enable COPR repositories
community.general.copr:
name: "{{ item.repo | default(item) }}"
state: enabled
loop: "{{ (dnf_add_copr | default([])) + (dnf_add_copr_group | default([])) }}"
become: true
- name: Add DNF repositories
ansible.builtin.yum_repository:
name: "{{ item.name }}"
description: "{{ item.description | default(omit) }}"
baseurl: "{{ item.baseurl }}"
enabled: true
gpgcheck: "{{ item.gpgcheck | default(true) }}"
gpgkey: "{{ item.gpgkey | default(omit) }}"
loop: "{{ (dnf_add_repos | default([])) + (dnf_add_repos_group | default([])) }}"
become: true
- name: Add DNF repository files
ansible.builtin.get_url:
url: "{{ item.url }}"
dest: "/etc/yum.repos.d/{{ item.url | basename }}"
owner: root
group: root
mode: '0644'
loop: "{{ (dnf_add_repofiles | default([])) + (dnf_add_repofiles_group | default([])) }}"
become: true
- name: Remove DNF repositories
ansible.builtin.yum_repository:
name: "{{ item }}"
state: absent
loop: "{{ (dnf_remove_repos | default([])) + (dnf_remove_repos_group | default([])) }}"
become: true
- name: Remove unwanted packages
ansible.builtin.dnf:
name: "{{ item }}"
state: absent
autoremove: true
loop: "{{ (dnf_remove | default([])) + (dnf_remove_group | default([])) }}"
become: true
failed_when: false
- name: Install DNF packages
ansible.builtin.dnf:
name: "{{ (dnf_install | default([])) + (dnf_install_group | default([])) }}"
state: present
skip_broken: true
become: true
when: ((dnf_install | default([])) + (dnf_install_group | default([]))) | length > 0
- name: Update all DNF packages
ansible.builtin.dnf:
name: "*"
state: latest # noqa package-latest
skip_broken: true
become: true
- name: Install cargo packages
ansible.builtin.command:
cmd: "cargo install {{ item }}"
loop: "{{ (cargo_packages | default([])) + (cargo_packages_group | default([])) }}"
when: ((cargo_packages | default([])) + (cargo_packages_group | default([]))) | length > 0
register: software_cargo_install_result
changed_when: "'Installing' in software_cargo_install_result.stderr or 'Compiling' in software_cargo_install_result.stderr"
failed_when: software_cargo_install_result.rc != 0 and 'already exists' not in software_cargo_install_result.stderr
- name: Clone git repositories
ansible.builtin.git:
repo: "{{ item.repo }}"
dest: "{{ item.dest }}"
version: "{{ item.version }}"
update: true
loop: "{{ (git_add_repos | default([])) + (git_add_repos_group | default([])) }}"

View File

@@ -1,5 +1,4 @@
--- ---
- name: Configure sysctl parameters - name: Configure sysctl parameters
ansible.posix.sysctl: ansible.posix.sysctl:
name: "{{ item.name }}" name: "{{ item.name }}"
@@ -7,23 +6,26 @@
sysctl_file: "{{ item.file }}" sysctl_file: "{{ item.file }}"
state: present state: present
reload: true reload: true
loop: "{{ sysconfig_sysctl }}" loop:
- name: fs.inotify.max_user_watches
value: 524288
file: /etc/sysctl.d/local.conf
become: true become: true
when: sysconfig_sysctl is defined and sysconfig_sysctl | length > 0
- name: Configure GNOME settings - name: Configure GNOME settings
community.general.dconf: community.general.dconf:
key: "/{{ item.schema | replace('.', '/') }}/{{ item.key }}" key: "/{{ item.schema | replace('.', '/') }}/{{ item.key }}"
value: "{{ item.value }}" value: "{{ item.value }}"
state: present state: present
loop: "{{ sysconfig_gsettings }}" loop:
when: sysconfig_gsettings is defined and sysconfig_gsettings | length > 0 - schema: org.gnome.nautilus.preferences
key: always-use-location-entry
value: "true"
- name: Configure sudoers for passwordless commands - name: Configure sudoers for passwordless commands
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: /etc/sudoers path: /etc/sudoers
line: "{{ ansible_facts['user_id'] }} ALL=(ALL) NOPASSWD: {{ sysconfig_sudoers_nopasswd_commands | join(', ') }}" line: "{{ ansible_user }} ALL=(ALL) NOPASSWD: /usr/bin/psd-overlay-helper, /usr/bin/btrfs, /usr/bin/journalctl, /usr/bin/dnf, /usr/bin/fwupdmgr, /usr/bin/dmesg"
state: present state: present
validate: /usr/sbin/visudo -cf %s validate: /usr/bin/visudo -cf %s
become: true become: true
when: sysconfig_sudoers_nopasswd_commands is defined and sysconfig_sudoers_nopasswd_commands | length > 0

View File

@@ -1,6 +1,13 @@
- name: Set user shell - name: Create user and set shell
ansible.builtin.user: ansible.builtin.user:
name: "{{ item.name }}" name: "{{ item.name }}"
shell: "{{ item.shell }}" shell: "{{ item.shell }}"
loop: "{{ users }}" loop: "{{ users }}"
become: true become: true
- name: Enable lingering for user services
ansible.builtin.command:
cmd: "loginctl enable-linger {{ item.name }}"
loop: "{{ users }}"
changed_when: false
become: true