From 9e94dfbc52063db517fa7a0aeb0c2fff4bb4b761 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Fri, 14 Feb 2025 19:46:01 +0100 Subject: [PATCH 01/14] Add incus server prep ansible stack --- ansible/ansible.cfg | 3 + ansible/files/incus.servers.tpl | 5 + ansible/files/incus.sources.tpl | 8 + ansible/files/zabbly-key.asc | 41 ++++ ansible/inventory | 1 + ansible/playbook.yaml | 324 ++++++++++++++++++++++++++++++++ 6 files changed, 382 insertions(+) create mode 100644 ansible/ansible.cfg create mode 100644 ansible/files/incus.servers.tpl create mode 100644 ansible/files/incus.sources.tpl create mode 100644 ansible/files/zabbly-key.asc create mode 100644 ansible/inventory create mode 100644 ansible/playbook.yaml diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..c491d26 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,3 @@ +[defaults] +remote_tmp = /tmp +inventory = inventory diff --git a/ansible/files/incus.servers.tpl b/ansible/files/incus.servers.tpl new file mode 100644 index 0000000..fa87357 --- /dev/null +++ b/ansible/files/incus.servers.tpl @@ -0,0 +1,5 @@ +{% for host in vars['ansible_play_hosts'] | sort %} +{% if hostvars[host]['incus_name'] == task_name and "cluster" in hostvars[host]['incus_roles'] %} +- {{ host }} +{% endif %} +{% endfor %} diff --git a/ansible/files/incus.sources.tpl b/ansible/files/incus.sources.tpl new file mode 100644 index 0000000..418f342 --- /dev/null +++ b/ansible/files/incus.sources.tpl @@ -0,0 +1,8 @@ +# Managed by Ansible, do not modify. +Enabled: yes +Types: deb +URIs: https://pkgs.zabbly.com/incus/{{ task_release }}/ +Suites: {{ ansible_distribution_release }} +Components: main +Architectures: {{ dpkg_architecture.stdout }} +Signed-By: /etc/apt/keyrings/ansible-zabbly.asc diff --git a/ansible/files/zabbly-key.asc b/ansible/files/zabbly-key.asc new file mode 100644 index 0000000..d7241ca --- /dev/null +++ b/ansible/files/zabbly-key.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBGTlYcIBDACYQoVXVyQ6Y3Of14GwEaiv/RstQ8jWnH441OtvDbD/VVT8yF0P +pUfypWjQS8aq0g32Qgb9H9+b8UAAKojA2W0szjJFlmmSq19YDMMmNC4AnfeZlKYM +61Zonna7fPaXmlsTlSiUeo/PGvmAXrkFURC9S8FbhZdWEcUpf9vcKAoEzV8qGA4J +xbKlj8EOjSkdq3OQ1hHjP8gynbbzMhZQwjbnWqoiPj35ed9EMn+0QcX+GmynGq6T +hBXdRdeQjZC6rmXzNF2opCyxqx3BJ0C7hUtpHegmeoH34wnJHCqGYkEKFAjlRLoW +tOzHY9J7OFvB6U7ENtnquj7lg2VQK+hti3uiHW+oide06QgjVw2irucCblQzphgo +iX5QJs7tgFFDsA9Ee0DZP6cu83hNFdDcXEZBc9MT5Iu0Ijvj7Oeym3DJpkCuIWgk +SeP56sp7333zrg73Ua7YZsZHRayAe/4YdNUua+90P4GD12TpTtJa4iRWRd7bis6m +tSkKRj7kxyTsxpEAEQEAAbQmWmFiYmx5IEtlcm5lbCBCdWlsZHMgPGluZm9AemFi +Ymx5LmNvbT6JAdQEEwEKAD4WIQRO/FkGlssVuHxzo62CzIeXyDjc/QUCZOVhwgIb +AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCCzIeXyDjc/W05C/4n +lGRTlyOETF2K8oWbjtan9wlttQ+pwymJCnP8T+JJDycGL8dPsGdG1ldHdorVZpFi +1P+Bem9bbiW73TpbX+WuCfP1g3WN7AVa2mYRfSVhsLNeBAMRgWgNW9JYsmg99lmY +aPsRYZdGu/PB+ffMIyWhjL3CKCbYS6lV5N5Mi4Lobyz/I1Euxpk2vJhhUqh786nJ +pQpDnvEl1CRANS6JD9bIvEdfatlAhFlrz1TTf6R7SlppyYI7tme4I/G3dnnHWYSG +cGRaLwpwobTq0UNSO71g7+at9eY8dh5nn2lZUvvxZvlbXoOoPxKUoeGVXqoq5F7S +QcMVAogYtyNlnLnsUfSPw6YFRaQ5o00h30bR3hk+YmJ47AJCRY9GIc/IEdSnd/Z5 +Ea7CrP2Bo4zxPgcl8fe311FQRTRoWr19l5PXZgGjzy6siXTrYQi6GjLtqVB5SjJf +rrIIy1vZRyDL96WPu6fS+XQMpjsSygj+DBFk8OAvHhQhMCXHgT4BMyg4D5GE0665 +AY0EZOVhwgEMAMIztf6WlRsweysb0tzktYE5E/GxIK1lwcD10Jzq3ovJJPa2Tg2t +J6ZBmMQfwU4OYO8lJxlgm7t6MYh41ZZaRhySCtbJiAXqK08LP9Gc1iWLRvKuMzli +NFSiFDFGT1D6kwucVfL/THxvZlQ559kK+LB4iXEKXz37r+MCX1K9uiv0wn63Vm0K +gD3HDgfXWYJcNyXXfJBe3/T5AhuSBOQcpa7Ow5n8zJ+OYg3FFKWHDBTSSZHpbJFr +ArMIGARz5/f+EVj9XGY4W/+ZJlxNh8FzrTLeRArmCWqKLPRG/KF36dTY7MDpOzlw +vu7frv+cgiXHZ2NfPrkH8oOl4L+ufze5KBGcN0QwFDcuwCkv/7Ft9Ta7gVaIBsK7 +12oHInUJ6EkBovxpuaLlHlP8IfmZLZbbHzR2gR0e6IhLtrzd7urB+gXUtp6+wCL+ +kWD14TTJhSQ+SFU8ajvUah7/1m2bxdjZNp9pzOPGkr/jEjCM0CpZiCY62SeIJqVc +4/ID9NYLAGmSIwARAQABiQG8BBgBCgAmFiEETvxZBpbLFbh8c6OtgsyHl8g43P0F +AmTlYcICGwwFCQPCZwAACgkQgsyHl8g43P0wEgv+LuknyXHpYpiUcJOl9Q5yLokd +o7tJwJ+9Fu7EDAfM7mPgyBj7Ad/v9RRP+JKWHqIYEjyrRnz9lmzciU+LT/CeoQu/ +MgpU8wRI4gVtLkX2238amrTKKlVjQUUNHf7cITivUs/8e5W21JfwvcSzu5z4Mxyw +L6vMlBUAixtzZSXD6O7MO9uggHUZMt5gDSPXG2RcIgWm0Bd1yTHL7jZt67xBgZ4d +hUoelMN2XIDLv4SY78jbHAqVN6CLLtWrz0f5YdaeYj8OT6Ohr/iJQdlfVaiY4ikp +DzagLi0LvG9/GuB9eO6yLuojg45JEH8DC7NW5VbdUITxQe9NQ/j5kaRKTEq0fyZ+ +qsrryTyvXghxK8oMUcI10l8d41qXDDPCA40kruuspCZSAle3zdqpYqiu6bglrgWr +Zr2Nm9ecm/kkqMIcyJ8e2mlkuufq5kVem0Oez+GIDegvwnK3HAqWQ9lzdWKvnLiE +gNkvg3bqIwZ/WoHBnSwOwwAzwarJl/gn8OG6CIeP +=8Uc6 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/ansible/inventory b/ansible/inventory new file mode 100644 index 0000000..040cf6c --- /dev/null +++ b/ansible/inventory @@ -0,0 +1 @@ +bob ansible_ssh_private_key_file=~/.ssh/keys/bob diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml new file mode 100644 index 0000000..1dd7d4c --- /dev/null +++ b/ansible/playbook.yaml @@ -0,0 +1,324 @@ +--- +- name: Update system + hosts: all + tasks: + - name: Ensure aptitude installed + apt: + name: "aptitude" + state: present + tags: + - apt + become: true + + - name: Ensure OS upgraded + apt: + upgrade: dist + tags: + - apt + - update + - os + become: true + + - name: Check if reboot is necessary + register: reboot_required_file + stat: + path: /var/run/reboot-required + get_checksum: false + tags: + - os + - reboot + notify: reboot host + + - name: All packages updated + apt: + name: "*" + state: latest # noqa 403 + tags: + - apt + - update + - packages + become: true + + handlers: + - name: Reboot host + reboot: + msg: "Reboot initiated by Ansible" + connect_timeout: 5 + reboot_timeout: 600 + pre_reboot_delay: 0 + post_reboot_delay: 30 + test_command: whoami + become: true + when: reboot_required_file.stat.exists + +- name: Incus - Add package repository (apt) + hosts: all + gather_facts: true + gather_subset: + - "distribution_release" + vars: + task_release: "{{ incus_release | default('stable') }}" + task_roles: "{{ incus_roles | default(['standalone', 'ui']) }}" + any_errors_fatal: true + tasks: + - name: Check if distribution is supported + meta: end_play + when: 'ansible_distribution not in ("Ubuntu", "Debian")' + + - name: Create apt keyring path + file: + path: /etc/apt/keyrings/ + mode: 0755 + state: directory + when: 'task_roles|length > 0 and task_release != "distro"' + + - name: Add Zabbly repository key + copy: + src: files/zabbly-key.asc + dest: /etc/apt/keyrings/ansible-zabbly.asc + notify: Update apt + become: true + when: 'task_roles|length > 0 and task_release != "distro"' + + - name: Get DPKG architecture + shell: dpkg --print-architecture + register: dpkg_architecture + changed_when: false + check_mode: no + when: 'task_roles|length > 0 and task_release != "distro"' + + - name: Add Zabbly package source + template: + src: files/incus.sources.tpl + dest: /etc/apt/sources.list.d/ansible-zabbly-incus-{{ task_release }}.sources + notify: Update apt + become: true + when: 'task_roles|length > 0 and task_release != "distro"' + + handlers: + - name: Update apt + apt: + force_apt_get: yes + update_cache: yes + cache_valid_time: 0 + become: true + +- name: Incus - Install packages and bootstrap + hosts: all + gather_facts: true + gather_subset: + - "default_ipv4" + - "default_ipv6" + - "distribution_release" + vars: + task_init: "{{ incus_init | default('{}') }}" + task_ip_address: "{{ incus_ip_address | default(ansible_default_ipv6['address'] | default(ansible_default_ipv4['address'])) }}" + task_name: "{{ incus_name | default('') }}" + task_roles: "{{ incus_roles | default(['ui', 'standalone']) }}" + + task_ovn_northbound: "{{ lookup('template', '../files/ovn/ovn-central.servers.tpl') | from_yaml | map('regex_replace', '^(.*)$', 'ssl:[\\1]:6641') | join(',') }}" + task_servers: "{{ lookup('template', 'files/incus.servers.tpl') | from_yaml | sort }}" + any_errors_fatal: true + become: true + tasks: + - name: Install the Incus package (deb) + apt: + name: + - incus + install_recommends: no + state: present + register: install_deb + when: 'ansible_distribution in ("Debian", "Ubuntu") and task_roles | length > 0' + + - name: Install the Incus package (rpm) + ansible.builtin.package: + name: + - incus + state: present + register: install_rpm + when: 'ansible_distribution == "CentOS" and task_roles | length > 0' + + - name: Install the Incus UI package (deb) + apt: + name: + - incus-ui-canonical + install_recommends: no + state: present + when: 'ansible_distribution in ("Debian", "Ubuntu") and "ui" in task_roles' + + # - name: Install btrfs tools + # ansible.builtin.package: + # name: + # - btrfs-progs + # state: present + # when: "task_roles | length > 0 and 'btrfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install ceph tools + # ansible.builtin.package: + # name: + # - ceph-common + # state: present + # when: "task_roles | length > 0 and 'ceph' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install LVM tools + # ansible.builtin.package: + # name: + # - lvm2 + # state: present + # when: "task_roles | length > 0 and 'lvm' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install ZFS dependencies + # ansible.builtin.package: + # name: + # - zfs-dkms + # state: present + # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver') and ansible_distribution == 'Debian'" + # + # - name: Install ZFS tools + # ansible.builtin.package: + # name: + # - zfsutils-linux + # state: present + # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" + + - name: Set uid allocation + shell: + cmd: "usermod root --add-subuids 10000000-1009999999" + when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' + + - name: Set gid allocation + shell: + cmd: "usermod root --add-subgids 10000000-1009999999" + when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' + + - name: Enable incus socket unit + systemd: + enabled: true + name: incus.socket + state: started + when: 'install_deb.changed or install_rpm.changed' + + - name: Enable incus service unit + systemd: + enabled: true + name: incus.service + state: started + when: 'install_deb.changed or install_rpm.changed' + + - name: Enable incus startup unit + systemd: + enabled: true + name: incus-startup.service + state: started + when: 'install_deb.changed or install_rpm.changed' + + - name: Set client listen address + shell: + cmd: "incus --force-local config set core.https_address {{ task_ip_address }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set cluster listen address + shell: + cmd: "incus --force-local config set cluster.https_address {{ task_ip_address }}" + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' + + # - name: Set OVN NorthBound database + # shell: + # cmd: "incus --force-local config set network.ovn.northbound_connection={{ task_ovn_northbound }} network.ovn.client_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.crt') }}\" network.ovn.client_key=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.key') }}\" network.ovn.ca_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/ca.crt') }}\"" + # notify: Restart Incus + # when: '(install_deb.changed or install_rpm.changed) and task_ovn_northbound and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Add networks + shell: + cmd: "incus network create {{ item.key }} --type={{ item.value.type }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" + loop: "{{ task_init['network'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set network description + shell: + cmd: "incus network set --property {{ item.key }} description=\"{{ item.value.description }}\"" + loop: "{{ task_init['network'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' + + - name: Add storage pools + shell: + cmd: "incus storage create {{ item.key }} {{ item.value.driver }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" + loop: "{{ task_init['storage'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set storage pool description + shell: + cmd: "incus storage set --property {{ item.key }} description=\"{{ item.value.description }}\"" + loop: "{{ task_init['storage'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' + + - name: Add storage pool to default profile + shell: + cmd: "incus profile device add default root disk path=/ pool={{ item }}" + loop: "{{ task_init['storage'] | dict2items | json_query('[?value.default].key') }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Add network to default profile + shell: + cmd: "incus profile device add default eth0 nic network={{ item }} name=eth0" + loop: "{{ task_init['network'] | dict2items | json_query('[?value.default].key') }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Bootstrap the cluster + shell: + cmd: "incus --force-local cluster enable {{ inventory_hostname }}" + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' + + - name: Create join tokens + delegate_to: "{{ task_servers[0] }}" + shell: + cmd: "incus --force-local --quiet cluster add {{ inventory_hostname }}" + register: cluster_add + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] != inventory_hostname' + + - name: Wait 5s to avoid token use before valid + ansible.builtin.wait_for: + timeout: 5 + delegate_to: localhost + when: 'cluster_add.changed' + + - name: Join the cluster + throttle: 1 + shell: + cmd: "incus --force-local admin init --preseed" + stdin: |- + cluster: + enabled: true + cluster_address: "{{ task_ip_address }}" + cluster_token: "{{ cluster_add.stdout }}" + server_address: "{{ task_ip_address }}" + member_config: {% for pool in task_init.storage %}{% for key in task_init.storage[pool].local_config | default([]) %} + + - entity: storage-pool + name: {{ pool }} + key: {{ key }} + value: {{ task_init.storage[pool].local_config[key] }}{% endfor %}{% endfor %}{% for network in task_init.network %}{% for key in task_init.network[network].local_config | default([]) %} + + - entity: network + name: {{ network }} + key: {{ key }} + value: {{ task_init.network[network].local_config[key] }}{% endfor %}{% endfor %} + when: 'cluster_add.changed' + + - name: Apply additional configuration + shell: + cmd: "incus config set {{ item.key }}=\"{{ item.value }}\"" + loop: "{{ task_init['config'] | default({}) | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Load client certificates + shell: + cmd: "incus config trust add-certificate --name \"{{ item.key }}\" --type={{ item.value.type | default('client') }} -" + stdin: "{{ item.value.certificate }}" + loop: "{{ task_init['clients'] | default({}) | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + handlers: + - name: Restart Incus + systemd: + name: incus.service + state: restarted From 122abbe723d603dc831c1c637fb061396eb66060 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 15 Jul 2025 08:16:48 +0200 Subject: [PATCH 02/14] Git ignore vaultpass --- ansible/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 ansible/.gitignore diff --git a/ansible/.gitignore b/ansible/.gitignore new file mode 100644 index 0000000..b784e05 --- /dev/null +++ b/ansible/.gitignore @@ -0,0 +1 @@ +vaultpass From 5c6314dc73b42ad4b6541e1517a901065fcc1c58 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 17 Feb 2025 21:40:31 +0100 Subject: [PATCH 03/14] Move arr yaml to arr subdirectory --- arr.yml => arr/arr.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename arr.yml => arr/arr.yml (100%) diff --git a/arr.yml b/arr/arr.yml similarity index 100% rename from arr.yml rename to arr/arr.yml From 0f8822e6321e783a202cd74c862a3dcf46d69088 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Thu, 20 Feb 2025 08:51:26 +0100 Subject: [PATCH 04/14] Set utility scan script to color mode by default --- paperless/scantopaperless.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperless/scantopaperless.sh b/paperless/scantopaperless.sh index 9f417b9..5c10c0c 100755 --- a/paperless/scantopaperless.sh +++ b/paperless/scantopaperless.sh @@ -5,7 +5,7 @@ # DEVICE="pixma:MG5400_BD2FD8000000" DEVICE="airscan:w0:CANON INC. MG5400 series" -scanimage -d "$DEVICE" --mode Gray --batch --batch-prompt --format=png --resolution=300 +scanimage -d "$DEVICE" --mode Color --batch --batch-prompt --format=png --resolution=300 # scanimage -d "$DEVICE" --mode Gray --batch --format=png --button-controlled=yes --resolution=300 # ensure correct order if we scan more than 9 pages From eaaa35de252101f3cb1c57b127e6147d2bed7f32 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 2 Apr 2025 20:49:41 +0200 Subject: [PATCH 05/14] Split incus role from playbook --- ansible/playbook.yaml | 335 +----------------- ansible/roles/arr/tasks/main.yaml | 11 + ansible/roles/arr/templates/arr.yml | 171 +++++++++ .../roles/incus-install/tasks/add-repo.yaml | 56 +++ .../roles/incus-install/tasks/bootstrap.yaml | 220 ++++++++++++ ansible/roles/incus-install/tasks/main.yaml | 7 + .../system}/files/incus.servers.tpl | 0 .../system}/files/incus.sources.tpl | 0 .../{ => roles/system}/files/zabbly-key.asc | 0 ansible/roles/system/handlers/main.yaml | 11 + ansible/roles/system/tasks/main.yaml | 38 ++ 11 files changed, 531 insertions(+), 318 deletions(-) create mode 100644 ansible/roles/arr/tasks/main.yaml create mode 100644 ansible/roles/arr/templates/arr.yml create mode 100644 ansible/roles/incus-install/tasks/add-repo.yaml create mode 100644 ansible/roles/incus-install/tasks/bootstrap.yaml create mode 100644 ansible/roles/incus-install/tasks/main.yaml rename ansible/{ => roles/system}/files/incus.servers.tpl (100%) rename ansible/{ => roles/system}/files/incus.sources.tpl (100%) rename ansible/{ => roles/system}/files/zabbly-key.asc (100%) create mode 100644 ansible/roles/system/handlers/main.yaml create mode 100644 ansible/roles/system/tasks/main.yaml diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml index 1dd7d4c..47d5fc5 100644 --- a/ansible/playbook.yaml +++ b/ansible/playbook.yaml @@ -1,324 +1,23 @@ --- -- name: Update system - hosts: all +- name: Prepare incus server host + hosts: incus_server tasks: - - name: Ensure aptitude installed - apt: - name: "aptitude" - state: present - tags: - - apt - become: true + - name: Prepare system + ansible.builtin.import_role: + name: system + tags: system - - name: Ensure OS upgraded - apt: - upgrade: dist - tags: - - apt - - update - - os - become: true + # FIXME: Role needs much fixup before it can run + # - name: Prepare incus + # ansible.builtin.import_role: + # name: incus-install + # tags: incus - - name: Check if reboot is necessary - register: reboot_required_file - stat: - path: /var/run/reboot-required - get_checksum: false - tags: - - os - - reboot - notify: reboot host - - name: All packages updated - apt: - name: "*" - state: latest # noqa 403 - tags: - - apt - - update - - packages - become: true - - handlers: - - name: Reboot host - reboot: - msg: "Reboot initiated by Ansible" - connect_timeout: 5 - reboot_timeout: 600 - pre_reboot_delay: 0 - post_reboot_delay: 30 - test_command: whoami - become: true - when: reboot_required_file.stat.exists - -- name: Incus - Add package repository (apt) - hosts: all - gather_facts: true - gather_subset: - - "distribution_release" - vars: - task_release: "{{ incus_release | default('stable') }}" - task_roles: "{{ incus_roles | default(['standalone', 'ui']) }}" - any_errors_fatal: true +- name: Prepare all docker hosted containers + hosts: docker_instance tasks: - - name: Check if distribution is supported - meta: end_play - when: 'ansible_distribution not in ("Ubuntu", "Debian")' - - - name: Create apt keyring path - file: - path: /etc/apt/keyrings/ - mode: 0755 - state: directory - when: 'task_roles|length > 0 and task_release != "distro"' - - - name: Add Zabbly repository key - copy: - src: files/zabbly-key.asc - dest: /etc/apt/keyrings/ansible-zabbly.asc - notify: Update apt - become: true - when: 'task_roles|length > 0 and task_release != "distro"' - - - name: Get DPKG architecture - shell: dpkg --print-architecture - register: dpkg_architecture - changed_when: false - check_mode: no - when: 'task_roles|length > 0 and task_release != "distro"' - - - name: Add Zabbly package source - template: - src: files/incus.sources.tpl - dest: /etc/apt/sources.list.d/ansible-zabbly-incus-{{ task_release }}.sources - notify: Update apt - become: true - when: 'task_roles|length > 0 and task_release != "distro"' - - handlers: - - name: Update apt - apt: - force_apt_get: yes - update_cache: yes - cache_valid_time: 0 - become: true - -- name: Incus - Install packages and bootstrap - hosts: all - gather_facts: true - gather_subset: - - "default_ipv4" - - "default_ipv6" - - "distribution_release" - vars: - task_init: "{{ incus_init | default('{}') }}" - task_ip_address: "{{ incus_ip_address | default(ansible_default_ipv6['address'] | default(ansible_default_ipv4['address'])) }}" - task_name: "{{ incus_name | default('') }}" - task_roles: "{{ incus_roles | default(['ui', 'standalone']) }}" - - task_ovn_northbound: "{{ lookup('template', '../files/ovn/ovn-central.servers.tpl') | from_yaml | map('regex_replace', '^(.*)$', 'ssl:[\\1]:6641') | join(',') }}" - task_servers: "{{ lookup('template', 'files/incus.servers.tpl') | from_yaml | sort }}" - any_errors_fatal: true - become: true - tasks: - - name: Install the Incus package (deb) - apt: - name: - - incus - install_recommends: no - state: present - register: install_deb - when: 'ansible_distribution in ("Debian", "Ubuntu") and task_roles | length > 0' - - - name: Install the Incus package (rpm) - ansible.builtin.package: - name: - - incus - state: present - register: install_rpm - when: 'ansible_distribution == "CentOS" and task_roles | length > 0' - - - name: Install the Incus UI package (deb) - apt: - name: - - incus-ui-canonical - install_recommends: no - state: present - when: 'ansible_distribution in ("Debian", "Ubuntu") and "ui" in task_roles' - - # - name: Install btrfs tools - # ansible.builtin.package: - # name: - # - btrfs-progs - # state: present - # when: "task_roles | length > 0 and 'btrfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" - # - # - name: Install ceph tools - # ansible.builtin.package: - # name: - # - ceph-common - # state: present - # when: "task_roles | length > 0 and 'ceph' in task_init['storage'] | dict2items | json_query('[].value.driver')" - # - # - name: Install LVM tools - # ansible.builtin.package: - # name: - # - lvm2 - # state: present - # when: "task_roles | length > 0 and 'lvm' in task_init['storage'] | dict2items | json_query('[].value.driver')" - # - # - name: Install ZFS dependencies - # ansible.builtin.package: - # name: - # - zfs-dkms - # state: present - # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver') and ansible_distribution == 'Debian'" - # - # - name: Install ZFS tools - # ansible.builtin.package: - # name: - # - zfsutils-linux - # state: present - # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" - - - name: Set uid allocation - shell: - cmd: "usermod root --add-subuids 10000000-1009999999" - when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' - - - name: Set gid allocation - shell: - cmd: "usermod root --add-subgids 10000000-1009999999" - when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' - - - name: Enable incus socket unit - systemd: - enabled: true - name: incus.socket - state: started - when: 'install_deb.changed or install_rpm.changed' - - - name: Enable incus service unit - systemd: - enabled: true - name: incus.service - state: started - when: 'install_deb.changed or install_rpm.changed' - - - name: Enable incus startup unit - systemd: - enabled: true - name: incus-startup.service - state: started - when: 'install_deb.changed or install_rpm.changed' - - - name: Set client listen address - shell: - cmd: "incus --force-local config set core.https_address {{ task_ip_address }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - - - name: Set cluster listen address - shell: - cmd: "incus --force-local config set cluster.https_address {{ task_ip_address }}" - when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' - - # - name: Set OVN NorthBound database - # shell: - # cmd: "incus --force-local config set network.ovn.northbound_connection={{ task_ovn_northbound }} network.ovn.client_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.crt') }}\" network.ovn.client_key=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.key') }}\" network.ovn.ca_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/ca.crt') }}\"" - # notify: Restart Incus - # when: '(install_deb.changed or install_rpm.changed) and task_ovn_northbound and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - - - name: Add networks - shell: - cmd: "incus network create {{ item.key }} --type={{ item.value.type }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" - loop: "{{ task_init['network'] | dict2items }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - - - name: Set network description - shell: - cmd: "incus network set --property {{ item.key }} description=\"{{ item.value.description }}\"" - loop: "{{ task_init['network'] | dict2items }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' - - - name: Add storage pools - shell: - cmd: "incus storage create {{ item.key }} {{ item.value.driver }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" - loop: "{{ task_init['storage'] | dict2items }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - - - name: Set storage pool description - shell: - cmd: "incus storage set --property {{ item.key }} description=\"{{ item.value.description }}\"" - loop: "{{ task_init['storage'] | dict2items }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' - - - name: Add storage pool to default profile - shell: - cmd: "incus profile device add default root disk path=/ pool={{ item }}" - loop: "{{ task_init['storage'] | dict2items | json_query('[?value.default].key') }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - - - name: Add network to default profile - shell: - cmd: "incus profile device add default eth0 nic network={{ item }} name=eth0" - loop: "{{ task_init['network'] | dict2items | json_query('[?value.default].key') }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - - - name: Bootstrap the cluster - shell: - cmd: "incus --force-local cluster enable {{ inventory_hostname }}" - when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' - - - name: Create join tokens - delegate_to: "{{ task_servers[0] }}" - shell: - cmd: "incus --force-local --quiet cluster add {{ inventory_hostname }}" - register: cluster_add - when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] != inventory_hostname' - - - name: Wait 5s to avoid token use before valid - ansible.builtin.wait_for: - timeout: 5 - delegate_to: localhost - when: 'cluster_add.changed' - - - name: Join the cluster - throttle: 1 - shell: - cmd: "incus --force-local admin init --preseed" - stdin: |- - cluster: - enabled: true - cluster_address: "{{ task_ip_address }}" - cluster_token: "{{ cluster_add.stdout }}" - server_address: "{{ task_ip_address }}" - member_config: {% for pool in task_init.storage %}{% for key in task_init.storage[pool].local_config | default([]) %} - - - entity: storage-pool - name: {{ pool }} - key: {{ key }} - value: {{ task_init.storage[pool].local_config[key] }}{% endfor %}{% endfor %}{% for network in task_init.network %}{% for key in task_init.network[network].local_config | default([]) %} - - - entity: network - name: {{ network }} - key: {{ key }} - value: {{ task_init.network[network].local_config[key] }}{% endfor %}{% endfor %} - when: 'cluster_add.changed' - - - name: Apply additional configuration - shell: - cmd: "incus config set {{ item.key }}=\"{{ item.value }}\"" - loop: "{{ task_init['config'] | default({}) | dict2items }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - - - name: Load client certificates - shell: - cmd: "incus config trust add-certificate --name \"{{ item.key }}\" --type={{ item.value.type | default('client') }} -" - stdin: "{{ item.value.certificate }}" - loop: "{{ task_init['clients'] | default({}) | dict2items }}" - when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' - handlers: - - name: Restart Incus - systemd: - name: incus.service - state: restarted + - name: Set up Arr stack + ansible.builtin.import_role: + name: arr + tags: arr diff --git a/ansible/roles/arr/tasks/main.yaml b/ansible/roles/arr/tasks/main.yaml new file mode 100644 index 0000000..5e303d9 --- /dev/null +++ b/ansible/roles/arr/tasks/main.yaml @@ -0,0 +1,11 @@ +--- +- name: Deploy wallabag to swarm + community.general.docker_stack: + name: arr + state: present + prune: true + compose: + - "{{ lookup('template', 'docker-stack.yml.j2') | from_yaml }}" + become: true + tags: + - docker-swarm diff --git a/ansible/roles/arr/templates/arr.yml b/ansible/roles/arr/templates/arr.yml new file mode 100644 index 0000000..9d765e1 --- /dev/null +++ b/ansible/roles/arr/templates/arr.yml @@ -0,0 +1,171 @@ +services: + sonarr: + container_name: sonarr + image: lscr.io/linuxserver/sonarr:latest + ports: + - 8989:8989 + environment: + - PUID=${PUID} + - PGID=${PGID} + - UMASK_SET=022 + - TZ=${TZ} + volumes: + - "./config/sonarr:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH:/data" + restart: unless-stopped + radarr: + container_name: radarr + image: lscr.io/linuxserver/radarr:latest + ports: + - 7878:7878 + environment: + - PUID=${PUID} + - PGID=${PGID} + - UMASK_SET=022 + - TZ=${TZ} + volumes: + - "./config/radarr:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH:/data" + restart: unless-stopped + lidarr: + container_name: lidarr + image: lscr.io/linuxserver/lidarr:latest + ports: + - 8686:8686 + environment: + - PUID=${PUID} + - PGID=${PGID} + - UMASK_SET=022 + - TZ=${TZ} + - DOCKER_MODS=linuxserver/mods:universal-docker + volumes: + - "./config/lidarr:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH:/data" + - "/var/run/docker.sock:/var/run/docker.sock:ro" + restart: unless-stopped + readarr: + container_name: readarr + image: lscr.io/linuxserver/readarr:develop + ports: + - 8787:8787 + environment: + - PUID=${PUID} + - PGID=${PGID} + - UMASK_SET=022 + - TZ=${TZ} + volumes: + - "./config/readarr:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH:/data" + restart: unless-stopped + prowlarr: + container_name: prowlarr + image: lscr.io/linuxserver/prowlarr:develop + environment: + - PUID=${PUID} + - PGID=${PGID} + - UMASK_SET=022 + - TZ=${TZ} + volumes: + - "./config/prowlarr:/config" + ports: + - 9696:9696 + restart: unless-stopped + sabnzbd: + container_name: sabnzbd + image: lscr.io/linuxserver/sabnzbd:latest + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + volumes: + - "./config/sabnzbd:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH/usenet:/data/usenet:rw" + ports: + - 8080:8080 + restart: unless-stopped + pia-qbittorrent: + image: j4ym0/pia-qbittorrent + container_name: pia-qbittorrent + cap_add: + - NET_ADMIN + environment: + - UID=${PUID} + - GID=${PGID} + - TZ=${TZ} + - REGION=Netherlands + - USER=${PIA_USER} + - PASSWORD=${PIA_PASS} + volumes: + - "./config/piaqbit:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH/torrent:/downloads:rw" + ports: + - "8888:8888" + restart: unless-stopped + jellyfin: + image: lscr.io/linuxserver/jellyfin:latest + container_name: jellyfin + environment: + - PUID={$PUID} + - PGID={$PGID} + - TZ=${TZ} + #- JELLYFIN_PublishedServerUrl=192.168.0.5 #optional + volumes: + - ".config/jellyfin:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH/media:/data" + ports: + - 8096:8096 + - 7359:7359/udp #optional - network discovery + - 1900:1900/udp #optional - dlna discovery + restart: unless-stopped + audiobookshelf: + container_name: audiobookshelf + image: ghcr.io/advplyr/audiobookshelf:latest + environment: + - PUID=${PUID} + - PGID=${PGID} + - UMASK_SET=022 + - TZ=${TZ} + ports: + - 13378:80 + volumes: + - "CHANGE_TO_COMPOSE_DATA_PATH/media/audio/books:/audiobooks" + - "CHANGE_TO_COMPOSE_DATA_PATH/media/audio/podcasts:/podcasts" + - ".config/audiobookshelf:/config" + - ".metadata/audiobookshelf:/metadata" + restart: unless-stopped + jellyseerr: + image: fallenbagel/jellyseerr:latest + container_name: jellyseerr + environment: + - TZ=${TZ} + ports: + - 5055:5055 + volumes: + - "./config/jellyseerr:/app/config" + restart: unless-stopped + beets: + image: lscr.io/linuxserver/beets:latest + container_name: beets + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + volumes: + - "./config/beets:/config" + - "CHANGE_TO_COMPOSE_DATA_PATH/media/audio/music:/music" + - "CHANGE_TO_COMPOSE_DATA_PATH/media/audio/music-unsorted:/downloads" + - "CHANGE_TO_COMPOSE_DATA_PATH:/data" + ports: + - 8337:8337 + restart: unless-stopped + homarr: + image: ghcr.io/ajnart/homarr:latest + container_name: homarr + volumes: + - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration + - ./config/homarr/configs:/app/data/configs + - ./config/homarr/icons:/app/public/icons + - ./config/homarr/data:/data + ports: + - '80:7575' + restart: unless-stopped diff --git a/ansible/roles/incus-install/tasks/add-repo.yaml b/ansible/roles/incus-install/tasks/add-repo.yaml new file mode 100644 index 0000000..edb19c9 --- /dev/null +++ b/ansible/roles/incus-install/tasks/add-repo.yaml @@ -0,0 +1,56 @@ +--- +- name: Incus - Add package repository (apt) + hosts: all + gather_facts: true + gather_subset: + - "distribution_release" + vars: + task_release: "{{ incus_release | default('stable') }}" + task_roles: "{{ incus_roles | default(['standalone', 'ui']) }}" + any_errors_fatal: true + tasks: + - name: Check if distribution is supported + ansible.builtin.meta: end_play + when: 'ansible_distribution not in ("Ubuntu", "Debian")' + + - name: Create apt keyring path + ansible.builtin.file: + path: /etc/apt/keyrings/ + mode: "0755" + state: directory + when: 'task_roles | length > 0 and task_release != "distro"' + + - name: Add Zabbly repository key + ansible.builtin.copy: + src: files/zabbly-key.asc + dest: /etc/apt/keyrings/ansible-zabbly.asc + mode: "0644" + notify: Update apt + become: true + when: 'task_roles | length > 0 and task_release != "distro"' + + - name: Get DPKG architecture + ansible.builtin.shell: dpkg --print-architecture + register: dpkg_architecture + changed_when: false + check_mode: false + when: 'task_roles | length > 0 and task_release != "distro"' + + - name: Add Zabbly package source + ansible.builtin.template: + src: files/incus.sources.tpl + dest: /etc/apt/sources.list.d/ansible-zabbly-incus-{{ task_release }}.sources + notify: Update apt + become: true + when: 'task_roles|length > 0 and task_release != "distro"' + + - name: Handle apt by flushing handlers + meta: flush_handlers + + handlers: + - name: Update apt + ansible.builtin.apt: + force_apt_get: true + update_cache: true + cache_valid_time: 0 + become: true diff --git a/ansible/roles/incus-install/tasks/bootstrap.yaml b/ansible/roles/incus-install/tasks/bootstrap.yaml new file mode 100644 index 0000000..c6e1dfa --- /dev/null +++ b/ansible/roles/incus-install/tasks/bootstrap.yaml @@ -0,0 +1,220 @@ +--- +- name: Incus - Install packages and bootstrap + hosts: all + gather_facts: true + gather_subset: + - "default_ipv4" + - "default_ipv6" + - "distribution_release" + vars: + task_init: "{{ incus_init | default('{}') }}" + task_ip_address: "{{ incus_ip_address | default(ansible_default_ipv6['address'] | default(ansible_default_ipv4['address'])) }}" + task_name: "{{ incus_name | default('') }}" + task_roles: "{{ incus_roles | default(['ui', 'standalone']) }}" + + task_ovn_northbound: "{{ lookup('template', '../files/ovn/ovn-central.servers.tpl') | from_yaml | map('regex_replace', '^(.*)$', 'ssl:[\\1]:6641') | join(',') }}" + task_servers: "{{ lookup('template', 'files/incus.servers.tpl') | from_yaml | sort }}" + any_errors_fatal: true + become: true + tasks: + - name: Install the Incus package (deb) + ansible.builtin.apt: + name: + - incus + install_recommends: no + state: present + register: install_deb + when: 'ansible_distribution in ("Debian", "Ubuntu") and task_roles | length > 0' + + - name: Install the Incus package (rpm) + ansible.builtin.package: + name: + - incus + state: present + register: install_rpm + when: 'ansible_distribution == "CentOS" and task_roles | length > 0' + + - name: Install the Incus UI package (deb) + ansible.builtin.apt: + name: + - incus-ui-canonical + install_recommends: no + state: present + when: 'ansible_distribution in ("Debian", "Ubuntu") and "ui" in task_roles' + + # - name: Install btrfs tools + # ansible.builtin.package: + # name: + # - btrfs-progs + # state: present + # when: "task_roles | length > 0 and 'btrfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install ceph tools + # ansible.builtin.package: + # name: + # - ceph-common + # state: present + # when: "task_roles | length > 0 and 'ceph' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install LVM tools + # ansible.builtin.package: + # name: + # - lvm2 + # state: present + # when: "task_roles | length > 0 and 'lvm' in task_init['storage'] | dict2items | json_query('[].value.driver')" + # + # - name: Install ZFS dependencies + # ansible.builtin.package: + # name: + # - zfs-dkms + # state: present + # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver') and ansible_distribution == 'Debian'" + # + # - name: Install ZFS tools + # ansible.builtin.package: + # name: + # - zfsutils-linux + # state: present + # when: "task_roles | length > 0 and 'zfs' in task_init['storage'] | dict2items | json_query('[].value.driver')" + + - name: Set uid allocation + ansible.builtin.shell: + cmd: "usermod root --add-subuids 10000000-1009999999" + when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' + + - name: Set gid allocation + ansible.builtin.shell: + cmd: "usermod root --add-subgids 10000000-1009999999" + when: '(install_deb.changed or install_rpm.changed) and ansible_distribution == "CentOS"' + + - name: Enable incus socket unit + ansible.builtin.systemd: + enabled: true + name: incus.socket + state: started + when: "install_deb.changed or install_rpm.changed" + + - name: Enable incus service unit + ansible.builtin.systemd: + enabled: true + name: incus.service + state: started + when: "install_deb.changed or install_rpm.changed" + + - name: Enable incus startup unit + ansible.builtin.systemd: + enabled: true + name: incus-startup.service + state: started + when: "install_deb.changed or install_rpm.changed" + + - name: Set client listen address + ansible.builtin.shell: + cmd: "incus --force-local config set core.https_address {{ task_ip_address }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set cluster listen address + ansible.builtin.shell: + cmd: "incus --force-local config set cluster.https_address {{ task_ip_address }}" + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' + + # - name: Set OVN NorthBound database + # shell: + # cmd: "incus --force-local config set network.ovn.northbound_connection={{ task_ovn_northbound }} network.ovn.client_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.crt') }}\" network.ovn.client_key=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/'+inventory_hostname+'.key') }}\" network.ovn.ca_cert=\"{{ lookup('file', '../data/ovn/'+ovn_name+'/ca.crt') }}\"" + # notify: Restart Incus + # when: '(install_deb.changed or install_rpm.changed) and task_ovn_northbound and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Add networks + ansible.builtin.shell: + cmd: "incus network create {{ item.key }} --type={{ item.value.type }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" + loop: "{{ task_init['network'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set network description + ansible.builtin.shell: + cmd: 'incus network set --property {{ item.key }} description="{{ item.value.description }}"' + loop: "{{ task_init['network'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' + + - name: Add storage pools + ansible.builtin.shell: + cmd: "incus storage create {{ item.key }} {{ item.value.driver }}{% for k in item.value.local_config | default([]) %} {{ k }}={{ item.value.local_config[k] }}{% endfor %}{% for k in item.value.config | default([]) %} {{ k }}={{ item.value.config[k] }}{% endfor %}" + loop: "{{ task_init['storage'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Set storage pool description + ansible.builtin.shell: + cmd: 'incus storage set --property {{ item.key }} description="{{ item.value.description }}"' + loop: "{{ task_init['storage'] | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname)) and item.value.description | default(None)' + + - name: Add storage pool to default profile + ansible.builtin.shell: + cmd: "incus profile device add default root disk path=/ pool={{ item }}" + loop: "{{ task_init['storage'] | dict2items | json_query('[?value.default].key') }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Add network to default profile + ansible.builtin.shell: + cmd: "incus profile device add default eth0 nic network={{ item }} name=eth0" + loop: "{{ task_init['network'] | dict2items | json_query('[?value.default].key') }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Bootstrap the cluster + ansible.builtin.shell: + cmd: "incus --force-local cluster enable {{ inventory_hostname }}" + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] == inventory_hostname' + + - name: Create join tokens + delegate_to: "{{ task_servers[0] }}" + ansible.builtin.shell: + cmd: "incus --force-local --quiet cluster add {{ inventory_hostname }}" + register: cluster_add + when: '(install_deb.changed or install_rpm.changed) and "cluster" in task_roles and task_servers[0] != inventory_hostname' + + - name: Wait 5s to avoid token use before valid + ansible.builtin.wait_for: + timeout: 5 + delegate_to: localhost + when: "cluster_add.changed" + + - name: Join the cluster + throttle: 1 + ansible.builtin.shell: + cmd: "incus --force-local admin init --preseed" + stdin: |- + cluster: + enabled: true + cluster_address: "{{ task_ip_address }}" + cluster_token: "{{ cluster_add.stdout }}" + server_address: "{{ task_ip_address }}" + member_config: {% for pool in task_init.storage %}{% for key in task_init.storage[pool].local_config | default([]) %} + + - entity: storage-pool + name: {{ pool }} + key: {{ key }} + value: {{ task_init.storage[pool].local_config[key] }}{% endfor %}{% endfor %}{% for network in task_init.network %}{% for key in task_init.network[network].local_config | default([]) %} + + - entity: network + name: {{ network }} + key: {{ key }} + value: {{ task_init.network[network].local_config[key] }}{% endfor %}{% endfor %} + when: "cluster_add.changed" + + - name: Apply additional configuration + ansible.builtin.shell: + cmd: 'incus config set {{ item.key }}="{{ item.value }}"' + loop: "{{ task_init['config'] | default({}) | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + + - name: Load client certificates + ansible.builtin.shell: + cmd: 'incus config trust add-certificate --name "{{ item.key }}" --type={{ item.value.type | default(''client'') }} -' + stdin: "{{ item.value.certificate }}" + loop: "{{ task_init['clients'] | default({}) | dict2items }}" + when: '(install_deb.changed or install_rpm.changed) and ("standalone" in task_roles or ("cluster" in task_roles and task_servers[0] == inventory_hostname))' + handlers: + - name: Restart Incus + ansible.builtin.systemd: + name: incus.service + state: restarted diff --git a/ansible/roles/incus-install/tasks/main.yaml b/ansible/roles/incus-install/tasks/main.yaml new file mode 100644 index 0000000..c465a5f --- /dev/null +++ b/ansible/roles/incus-install/tasks/main.yaml @@ -0,0 +1,7 @@ +--- +- name: "Add incus repository to system" + ansible.builtin.include_tasks: add-repo.yaml + + # TODO: Should presumably be split +- name: "Install and bootstrap incus" + ansible.builtin.include_tasks: bootstrap.yaml diff --git a/ansible/files/incus.servers.tpl b/ansible/roles/system/files/incus.servers.tpl similarity index 100% rename from ansible/files/incus.servers.tpl rename to ansible/roles/system/files/incus.servers.tpl diff --git a/ansible/files/incus.sources.tpl b/ansible/roles/system/files/incus.sources.tpl similarity index 100% rename from ansible/files/incus.sources.tpl rename to ansible/roles/system/files/incus.sources.tpl diff --git a/ansible/files/zabbly-key.asc b/ansible/roles/system/files/zabbly-key.asc similarity index 100% rename from ansible/files/zabbly-key.asc rename to ansible/roles/system/files/zabbly-key.asc diff --git a/ansible/roles/system/handlers/main.yaml b/ansible/roles/system/handlers/main.yaml new file mode 100644 index 0000000..e1aa98b --- /dev/null +++ b/ansible/roles/system/handlers/main.yaml @@ -0,0 +1,11 @@ +--- +- name: Reboot host + ansible.builtin.reboot: + msg: "Reboot initiated by Ansible" + connect_timeout: 5 + reboot_timeout: 600 + pre_reboot_delay: 0 + post_reboot_delay: 30 + test_command: whoami + become: true + when: reboot_required_file.stat.exists diff --git a/ansible/roles/system/tasks/main.yaml b/ansible/roles/system/tasks/main.yaml new file mode 100644 index 0000000..068c2e0 --- /dev/null +++ b/ansible/roles/system/tasks/main.yaml @@ -0,0 +1,38 @@ +--- +- name: Ensure aptitude installed + ansible.builtin.apt: + name: "aptitude" + state: present + tags: + - apt + become: true + +- name: Ensure OS upgraded + ansible.builtin.apt: + upgrade: dist + tags: + - apt + - update + - os + become: true + +- name: Check if reboot is necessary + register: reboot_required_file + ansible.builtin.stat: + path: /var/run/reboot-required + get_checksum: false + tags: + - os + - reboot + notify: Reboot host + +- name: All system packages updated + ansible.builtin.apt: + name: "*" + state: latest # noqa package-latest + tags: + - apt + - update + - packages + become: true + From 71244751c74b5d86365a5592511036cd6fd1f3c8 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Sat, 12 Apr 2025 09:39:39 +0200 Subject: [PATCH 06/14] Split arr role from playbook --- ansible/playbook.yaml | 8 + ansible/roles/arr/README.md | 38 ++++ ansible/roles/arr/defaults/main.yml | 9 + ansible/roles/arr/handlers/main.yml | 2 + ansible/roles/arr/meta/main.yml | 26 +++ ansible/roles/arr/tasks/main.yaml | 11 - ansible/roles/arr/tasks/main.yml | 55 +++++ .../arr/templates/docker-compose.yaml.j2 | 209 ++++++++++++++++++ ansible/roles/arr/tests/inventory | 2 + ansible/roles/arr/tests/test.yml | 5 + ansible/roles/arr/vars/main.yml | 2 + 11 files changed, 356 insertions(+), 11 deletions(-) create mode 100644 ansible/roles/arr/README.md create mode 100644 ansible/roles/arr/defaults/main.yml create mode 100644 ansible/roles/arr/handlers/main.yml create mode 100644 ansible/roles/arr/meta/main.yml delete mode 100644 ansible/roles/arr/tasks/main.yaml create mode 100644 ansible/roles/arr/tasks/main.yml create mode 100644 ansible/roles/arr/templates/docker-compose.yaml.j2 create mode 100644 ansible/roles/arr/tests/inventory create mode 100644 ansible/roles/arr/tests/test.yml create mode 100644 ansible/roles/arr/vars/main.yml diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml index 47d5fc5..23e529d 100644 --- a/ansible/playbook.yaml +++ b/ansible/playbook.yaml @@ -13,6 +13,14 @@ # name: incus-install # tags: incus + # ansible-galaxy install geerlingguy.docker +- name: Install docker + hosts: docker_instance + tasks: + - name: Install docker and docker compose + ansible.builtin.import_role: + name: geerlingguy.docker + tags: docker - name: Prepare all docker hosted containers hosts: docker_instance diff --git a/ansible/roles/arr/README.md b/ansible/roles/arr/README.md new file mode 100644 index 0000000..225dd44 --- /dev/null +++ b/ansible/roles/arr/README.md @@ -0,0 +1,38 @@ +Role Name +========= + +A brief description of the role goes here. + +Requirements +------------ + +Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. + +Role Variables +-------------- + +A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. + +Dependencies +------------ + +A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. + +Example Playbook +---------------- + +Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: + + - hosts: servers + roles: + - { role: username.rolename, x: 42 } + +License +------- + +BSD + +Author Information +------------------ + +An optional section for the role authors to include contact information, or a website (HTML is not allowed). diff --git a/ansible/roles/arr/defaults/main.yml b/ansible/roles/arr/defaults/main.yml new file mode 100644 index 0000000..5506ed5 --- /dev/null +++ b/ansible/roles/arr/defaults/main.yml @@ -0,0 +1,9 @@ +--- + +arrstack_env_dir: /opt/arrstack + +arrstack_data_dir: /srv +arrstack_data_dir_create: true +arrstack_data_dir_owner: 1000 +arrstack_data_dir_group: 1000 + diff --git a/ansible/roles/arr/handlers/main.yml b/ansible/roles/arr/handlers/main.yml new file mode 100644 index 0000000..c3472e0 --- /dev/null +++ b/ansible/roles/arr/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for arr diff --git a/ansible/roles/arr/meta/main.yml b/ansible/roles/arr/meta/main.yml new file mode 100644 index 0000000..a8f4f00 --- /dev/null +++ b/ansible/roles/arr/meta/main.yml @@ -0,0 +1,26 @@ +galaxy_info: + author: Marty Oehme + description: Deploying a full *arr stack + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + license: GPL-3.0-only + + min_ansible_version: "2.1" + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. diff --git a/ansible/roles/arr/tasks/main.yaml b/ansible/roles/arr/tasks/main.yaml deleted file mode 100644 index 5e303d9..0000000 --- a/ansible/roles/arr/tasks/main.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -- name: Deploy wallabag to swarm - community.general.docker_stack: - name: arr - state: present - prune: true - compose: - - "{{ lookup('template', 'docker-stack.yml.j2') | from_yaml }}" - become: true - tags: - - docker-swarm diff --git a/ansible/roles/arr/tasks/main.yml b/ansible/roles/arr/tasks/main.yml new file mode 100644 index 0000000..2e5e2fe --- /dev/null +++ b/ansible/roles/arr/tasks/main.yml @@ -0,0 +1,55 @@ +--- +- name: Create Arr stack environment directory + ansible.builtin.file: + state: directory + path: "{{ arrstack_env_dir }}" + owner: root + group: root + mode: 0700 + +- name: Create Arr stack data directory + ansible.builtin.file: + state: directory + path: "{{ arrstack_data_dir }}/{{ item }}" + owner: "{{ arrstack_data_dir_owner }}" + group: "{{ arrstack_data_dir_group }}" + mode: 0770 + when: arrstack_data_dir_create + loop: + - "" + - files + - files/torrents + - files/usenet + - media + - media/tv + - media/movies + - media/music + - media/audiobooks + +# - name: Create Docker Compose environment file +# ansible.builtin.template: +# src: docker-compose.yml.j2 +# dest: "{{ arrstack_env_dir }}/docker-compose.yml" +# owner: root +# group: root +# mode: 0600 + +- name: Install pyyaml # necessary for compose_v2 + ansible.builtin.package: + name: python3-yaml + state: present + +- name: Start the compose stack + community.docker.docker_compose_v2: + project_name: arr + # project_src: "{{ arrstack_env_dir }}" + definition: "{{ lookup('template', 'docker-compose.yaml.j2') | from_yaml }}" + wait: true + wait_timeout: 60 + # services: + # - transmission + # - flaresolverr + # - sonarr-hd + # - sonarr-4k + # - sonarr-anime + # - prowlarr diff --git a/ansible/roles/arr/templates/docker-compose.yaml.j2 b/ansible/roles/arr/templates/docker-compose.yaml.j2 new file mode 100644 index 0000000..ec65b35 --- /dev/null +++ b/ansible/roles/arr/templates/docker-compose.yaml.j2 @@ -0,0 +1,209 @@ +services: + whoami: + container_name: whoami + image: traefik/whoami + ports: + - 80:80 + + sonarr: + container_name: sonarr + image: lscr.io/linuxserver/sonarr:latest + ports: + - 8989:8989 + env_file: + - arr.env + volumes: + - "./config/sonarr:/config" + - "{{ arrstack_data_dir }}/media/tv:/data/media/tv" + - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" + - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" + restart: unless-stopped + radarr: + container_name: radarr + image: lscr.io/linuxserver/radarr:latest + ports: + - 7878:7878 + env_file: + - arr.env + volumes: + - "./config/radarr:/config" + - "/mnt/ext/data/media/movies:/data/media/movies" # FIXME: Find solution + - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" + - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" + restart: unless-stopped + lidarr: + container_name: lidarr + image: lscr.io/linuxserver/lidarr:latest + ports: + - 8686:8686 + env_file: + - arr.env + - mb.env + environment: + - DOCKER_MODS=linuxserver/mods:universal-docker + volumes: + - "./config/lidarr:/config" + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "{{ arrstack_data_dir }}/media/music:/data/media/music" + - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" + - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" + restart: unless-stopped + readarr: + container_name: readarr + image: lscr.io/linuxserver/readarr:develop + ports: + - 8787:8787 + env_file: + - arr.env + volumes: + - "./config/readarr:/config" + - "{{ arrstack_data_dir }}/media/audiobooks:/data/media/audiobooks" + - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" + - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" + restart: unless-stopped + prowlarr: + container_name: prowlarr + image: lscr.io/linuxserver/prowlarr:develop + env_file: + - arr.env + volumes: + - "./config/prowlarr:/config" + ports: + - 9696:9696 + restart: unless-stopped + sabnzbd: + container_name: sabnzbd + image: lscr.io/linuxserver/sabnzbd:latest + env_file: + - arr.env + volumes: + - "./config/sabnzbd:/config" + - "{{ arrstack_data_dir }}/files/usenet:/data/usenet:rw" + ports: + - 8080:8080 + restart: unless-stopped + vpn: + image: qmcgaw/gluetun:v3 + container_name: vpn + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + env_file: + - arr.env + - pia.env + environment: + - VPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port + - FIREWALL_OUTBOUND_SUBNETS=172.18.0.0/24 + - BLOCK_SURVEILLANCE=on + volumes: + - "./config/gluetun:/gluetun" + ports: + - 8000:8000 # gluetun http control + - 8888:8888 # qBittorrent WebUI + restart: unless-stopped + qbittorrent: + image: linuxserver/qbittorrent + container_name: qbittorrent + env_file: + - arr.env + environment: + - WEBUI_PORT=8888 + volumes: + - "./config/piaqbit:/config" + - "./config/gluetun:/gluetun" + - "{{ arrstack_data_dir }}/files/torrent:/downloads" + depends_on: + - vpn + network_mode: "service:vpn" + restart: unless-stopped + gluetun-qbittorrent-port-manager: + image: patrickaclark/gluetun-qbittorrent-port-manager:latest + container_name: qbit-port-manager + env_file: + - arr.env + - pia.env + environment: + - QBITTORRENT_SERVER=localhost # IP Address of qbittorrent + - QBITTORRENT_PORT=8888 + - PORT_FORWARDED=/gluetun/forwarded_port + - HTTP_S=http # Select 'http' or 'https' depending on if you use certificates. + - GLUETUN_HOST=localhost # IP or FQDN of gluetun control server + - GLUETUN_PORT=8000 # port of gluetun control server + - RECHECK_TIME=60 # number of seconds between checks to gluetun server for port + volumes: + - "./config/gluetun:/gluetun" + depends_on: + - vpn + network_mode: "service:vpn" + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-H", "Authorization: $controlServerAuthKey", "-s", "http://localhost:8000/v1/openvpn/status", "|", "grep", "-q", '{"status":"running"}'] + interval: 30s + timeout: 10s + start_period: 60s + retries: 3 + jellyfin: + image: lscr.io/linuxserver/jellyfin:latest + container_name: jellyfin + env_file: + - arr.env + #environment: + #- JELLYFIN_PublishedServerUrl=192.168.0.5 #optional + volumes: + - ".config/jellyfin:/config" + - "/mnt/ext/data/media/movies:/media/movies" # FIXME: To be changed? + - "{{ arrstack_data_dir }}/media/tv:/media/tv" + - "{{ arrstack_data_dir }}/media/music:/media/music" + ports: + - 8096:8096 + - 7359:7359/udp #optional - network discovery + - 1900:1900/udp #optional - dlna discovery + restart: unless-stopped + audiobookshelf: + container_name: audiobookshelf + image: ghcr.io/advplyr/audiobookshelf:latest + env_file: + - arr.env + ports: + - 13378:80 + volumes: + - "{{ arrstack_data_dir }}/media/audiobooks:/audiobooks" + # - "{{ arrstack_data_dir }}/media/podcasts:/podcasts" # TODO: If integrating podcasts + - ".config/audiobookshelf:/config" + - ".metadata/audiobookshelf:/metadata" + restart: unless-stopped + jellyseerr: + image: fallenbagel/jellyseerr:latest + container_name: jellyseerr + env_file: + - arr.env + ports: + - 5055:5055 + volumes: + - "./config/jellyseerr:/app/config" + restart: unless-stopped + beets: + image: lscr.io/linuxserver/beets:latest + container_name: beets + env_file: + - arr.env + - mb.env + volumes: + - "./config/beets:/config" + - "{{ arrstack_data_dir }}/media/music:/music" + - "{{ arrstack_data_dir }}/files/music-unsorted:/downloads" + ports: + - 8337:8337 + restart: unless-stopped + homarr: + image: ghcr.io/ajnart/homarr:latest + container_name: homarr + volumes: + - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration + - ./config/homarr/configs:/app/data/configs + - ./config/homarr/icons:/app/public/icons + - ./config/homarr/data:/data + ports: + - '80:7575' + restart: unless-stopped diff --git a/ansible/roles/arr/tests/inventory b/ansible/roles/arr/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/ansible/roles/arr/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/ansible/roles/arr/tests/test.yml b/ansible/roles/arr/tests/test.yml new file mode 100644 index 0000000..0b31064 --- /dev/null +++ b/ansible/roles/arr/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - arr diff --git a/ansible/roles/arr/vars/main.yml b/ansible/roles/arr/vars/main.yml new file mode 100644 index 0000000..c17c9bb --- /dev/null +++ b/ansible/roles/arr/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for arr From b54d14c98e6b421229ce046b9f232a6c3209d923 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 14 Jul 2025 11:21:54 +0200 Subject: [PATCH 07/14] Rename host groups to host/instance --- ansible/inventory | 4 ++++ ansible/playbook.yaml | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ansible/inventory b/ansible/inventory index 040cf6c..8fea81f 100644 --- a/ansible/inventory +++ b/ansible/inventory @@ -1 +1,5 @@ +[host_system] bob ansible_ssh_private_key_file=~/.ssh/keys/bob + +[instance_system] +ansibletest ansible_connection=docker diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml index 23e529d..45a17b4 100644 --- a/ansible/playbook.yaml +++ b/ansible/playbook.yaml @@ -1,6 +1,6 @@ --- - name: Prepare incus server host - hosts: incus_server + hosts: host_system tasks: - name: Prepare system ansible.builtin.import_role: @@ -15,7 +15,7 @@ # ansible-galaxy install geerlingguy.docker - name: Install docker - hosts: docker_instance + hosts: instance_system tasks: - name: Install docker and docker compose ansible.builtin.import_role: @@ -23,7 +23,7 @@ tags: docker - name: Prepare all docker hosted containers - hosts: docker_instance + hosts: instance_system tasks: - name: Set up Arr stack ansible.builtin.import_role: From d930094638452d5de670e19882d89c945ffb42d5 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 14 Jul 2025 10:01:19 +0200 Subject: [PATCH 08/14] Install instance req for ansible docker tasks --- ansible/roles/arr/tasks/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ansible/roles/arr/tasks/main.yml b/ansible/roles/arr/tasks/main.yml index 2e5e2fe..34ba9f5 100644 --- a/ansible/roles/arr/tasks/main.yml +++ b/ansible/roles/arr/tasks/main.yml @@ -34,10 +34,13 @@ # group: root # mode: 0600 -- name: Install pyyaml # necessary for compose_v2 +- name: Install python requirements ansible.builtin.package: - name: python3-yaml + name: "{{ item }}" state: present + loop: + - python3-yaml # for docker compose_v2 + - python3-requests # for docker network - name: Start the compose stack community.docker.docker_compose_v2: From ec91e97fed1880a82b303170d55b513fa0ddb289 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 14 Jul 2025 10:01:19 +0200 Subject: [PATCH 09/14] Remove orphans on stack deployment --- ansible/roles/arr/tasks/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ansible/roles/arr/tasks/main.yml b/ansible/roles/arr/tasks/main.yml index 34ba9f5..3e7e79c 100644 --- a/ansible/roles/arr/tasks/main.yml +++ b/ansible/roles/arr/tasks/main.yml @@ -47,6 +47,7 @@ project_name: arr # project_src: "{{ arrstack_env_dir }}" definition: "{{ lookup('template', 'docker-compose.yaml.j2') | from_yaml }}" + remove_orphans: true wait: true wait_timeout: 60 # services: From e6b6154043c80b7d84d6889d239a8f3ee63db062 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 14 Jul 2025 10:01:19 +0200 Subject: [PATCH 10/14] Add caddy reverse proxy role Acts as reverse proxy for the docker instance. Can be configured through docker labels. Proxies anything that is received on port 80 or 443. --- ansible/playbook.yaml | 5 ++++ ansible/roles/caddy/tasks/main.yaml | 19 ++++++++++++ .../caddy/templates/docker-compose.yaml.j2 | 30 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 ansible/roles/caddy/tasks/main.yaml create mode 100644 ansible/roles/caddy/templates/docker-compose.yaml.j2 diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml index 45a17b4..0fe9fcc 100644 --- a/ansible/playbook.yaml +++ b/ansible/playbook.yaml @@ -25,6 +25,11 @@ - name: Prepare all docker hosted containers hosts: instance_system tasks: + - name: Set up Caddy stack + ansible.builtin.import_role: + name: caddy + tags: caddy + - name: Set up Arr stack ansible.builtin.import_role: name: arr diff --git a/ansible/roles/caddy/tasks/main.yaml b/ansible/roles/caddy/tasks/main.yaml new file mode 100644 index 0000000..0871a8e --- /dev/null +++ b/ansible/roles/caddy/tasks/main.yaml @@ -0,0 +1,19 @@ +- name: Install python requirements + ansible.builtin.package: + name: "{{ item }}" + state: present + loop: + - python3-yaml # for docker compose_v2 + - python3-requests # for docker network + +- name: Ensure caddy network exists + community.docker.docker_network: + name: caddy + +- name: Start the compose stack + community.docker.docker_compose_v2: + project_name: caddy + definition: "{{ lookup('template', 'docker-compose.yaml.j2') | from_yaml }}" + remove_orphans: true + wait: true + wait_timeout: 60 diff --git a/ansible/roles/caddy/templates/docker-compose.yaml.j2 b/ansible/roles/caddy/templates/docker-compose.yaml.j2 new file mode 100644 index 0000000..9f47f5a --- /dev/null +++ b/ansible/roles/caddy/templates/docker-compose.yaml.j2 @@ -0,0 +1,30 @@ +services: + caddy: + image: lucaslorentz/caddy-docker-proxy:ci-alpine + ports: + - 80:80 + - 443:443 + networks: + - caddy + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - caddy_data:/caddy + labels: + caddy.auto_https: "off" + + whoami: + container_name: whoami + image: traefik/whoami + networks: + - caddy + labels: + caddy: "http://test.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 80{{ '}}'}}" # has to be done to prevent ansible templating + +networks: + caddy: + external: true + +volumes: + caddy_data: {} + From 578f699cb74e98d442e0be835b99135406080133 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 14 Jul 2025 17:01:22 +0200 Subject: [PATCH 11/14] Move ansible docker module python requirements to docker tasks --- ansible/playbook.yaml | 8 ++++++++ ansible/roles/arr/tasks/main.yml | 8 -------- ansible/roles/caddy/tasks/main.yaml | 8 -------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/ansible/playbook.yaml b/ansible/playbook.yaml index 0fe9fcc..38e61ca 100644 --- a/ansible/playbook.yaml +++ b/ansible/playbook.yaml @@ -22,6 +22,14 @@ name: geerlingguy.docker tags: docker + - name: Install docker python requirements + ansible.builtin.package: + name: "{{ item }}" + state: present + loop: + - python3-yaml # for docker compose_v2 + - python3-requests # for docker network + - name: Prepare all docker hosted containers hosts: instance_system tasks: diff --git a/ansible/roles/arr/tasks/main.yml b/ansible/roles/arr/tasks/main.yml index 3e7e79c..2125de2 100644 --- a/ansible/roles/arr/tasks/main.yml +++ b/ansible/roles/arr/tasks/main.yml @@ -34,14 +34,6 @@ # group: root # mode: 0600 -- name: Install python requirements - ansible.builtin.package: - name: "{{ item }}" - state: present - loop: - - python3-yaml # for docker compose_v2 - - python3-requests # for docker network - - name: Start the compose stack community.docker.docker_compose_v2: project_name: arr diff --git a/ansible/roles/caddy/tasks/main.yaml b/ansible/roles/caddy/tasks/main.yaml index 0871a8e..61c1206 100644 --- a/ansible/roles/caddy/tasks/main.yaml +++ b/ansible/roles/caddy/tasks/main.yaml @@ -1,11 +1,3 @@ -- name: Install python requirements - ansible.builtin.package: - name: "{{ item }}" - state: present - loop: - - python3-yaml # for docker compose_v2 - - python3-requests # for docker network - - name: Ensure caddy network exists community.docker.docker_network: name: caddy From 93ae62dc93ceb72b84b4e5929230b73594267184 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 14 Jul 2025 10:01:19 +0200 Subject: [PATCH 12/14] Move to incus connection --- ansible/inventory | 3 ++- ansible/roles/arr/tasks/main.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ansible/inventory b/ansible/inventory index 8fea81f..a32928d 100644 --- a/ansible/inventory +++ b/ansible/inventory @@ -2,4 +2,5 @@ bob ansible_ssh_private_key_file=~/.ssh/keys/bob [instance_system] -ansibletest ansible_connection=docker +#ansibletest ansible_connection=docker +dockerbob ansible_connection=community.general.incus ansible_incus_remote=bob diff --git a/ansible/roles/arr/tasks/main.yml b/ansible/roles/arr/tasks/main.yml index 2125de2..f446a30 100644 --- a/ansible/roles/arr/tasks/main.yml +++ b/ansible/roles/arr/tasks/main.yml @@ -36,7 +36,7 @@ - name: Start the compose stack community.docker.docker_compose_v2: - project_name: arr + project_name: arrstack # project_src: "{{ arrstack_env_dir }}" definition: "{{ lookup('template', 'docker-compose.yaml.j2') | from_yaml }}" remove_orphans: true From 2045f4ae58628e200f9db5c85067ddade3a92a64 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 14 Jul 2025 10:01:19 +0200 Subject: [PATCH 13/14] Make arrstack docker compose ansible ready Move sonarr to new system Move sabnzbd to new system Move radarr Move lidarr !unsafe directive makes ansible ignore potential template vars in the string https://github.com/ansible/ansible/issues/16443 Move readarr Move prowlarr Move homarr Move beets Move jellyseerr Move audiobookshelf Move jellyfin Move gonic Migrate torrent setup --- ansible/roles/arr/defaults/main.yml | 9 +- ansible/roles/arr/tasks/main.yml | 12 +- .../arr/templates/docker-compose.yaml.j2 | 362 ++++++++++++------ 3 files changed, 254 insertions(+), 129 deletions(-) diff --git a/ansible/roles/arr/defaults/main.yml b/ansible/roles/arr/defaults/main.yml index 5506ed5..47ea10a 100644 --- a/ansible/roles/arr/defaults/main.yml +++ b/ansible/roles/arr/defaults/main.yml @@ -4,6 +4,11 @@ arrstack_env_dir: /opt/arrstack arrstack_data_dir: /srv arrstack_data_dir_create: true -arrstack_data_dir_owner: 1000 -arrstack_data_dir_group: 1000 +arrstack_puid: 1000 +arrstack_pgid: 100 +arrstack_tz: Europe/Berlin +arrstack_umask_set: 022 + +# arrstack_mb_user: Musicbrainz-user +# arrstack_mb_pass: Musicbrainz-password diff --git a/ansible/roles/arr/tasks/main.yml b/ansible/roles/arr/tasks/main.yml index f446a30..f2710ef 100644 --- a/ansible/roles/arr/tasks/main.yml +++ b/ansible/roles/arr/tasks/main.yml @@ -11,8 +11,8 @@ ansible.builtin.file: state: directory path: "{{ arrstack_data_dir }}/{{ item }}" - owner: "{{ arrstack_data_dir_owner }}" - group: "{{ arrstack_data_dir_group }}" + owner: "{{ arrstack_puid }}" + group: "{{ arrstack_pgid }}" mode: 0770 when: arrstack_data_dir_create loop: @@ -26,14 +26,6 @@ - media/music - media/audiobooks -# - name: Create Docker Compose environment file -# ansible.builtin.template: -# src: docker-compose.yml.j2 -# dest: "{{ arrstack_env_dir }}/docker-compose.yml" -# owner: root -# group: root -# mode: 0600 - - name: Start the compose stack community.docker.docker_compose_v2: project_name: arrstack diff --git a/ansible/roles/arr/templates/docker-compose.yaml.j2 b/ansible/roles/arr/templates/docker-compose.yaml.j2 index ec65b35..00f0252 100644 --- a/ansible/roles/arr/templates/docker-compose.yaml.j2 +++ b/ansible/roles/arr/templates/docker-compose.yaml.j2 @@ -1,117 +1,191 @@ services: - whoami: - container_name: whoami - image: traefik/whoami - ports: - - 80:80 - sonarr: container_name: sonarr image: lscr.io/linuxserver/sonarr:latest - ports: - - 8989:8989 - env_file: - - arr.env + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} volumes: - - "./config/sonarr:/config" + - "{{ arrstack_env_dir }}/config/sonarr:/config" - "{{ arrstack_data_dir }}/media/tv:/data/media/tv" - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" restart: unless-stopped + labels: + caddy: "http://sonarr.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 8989{{ '}}'}}" + radarr: container_name: radarr image: lscr.io/linuxserver/radarr:latest - ports: - - 7878:7878 - env_file: - - arr.env + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} volumes: - - "./config/radarr:/config" + - "{{ arrstack_env_dir }}/config/radarr:/config" - "/mnt/ext/data/media/movies:/data/media/movies" # FIXME: Find solution - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" restart: unless-stopped + labels: + caddy: "http://radarr.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 7878{{ '}}'}}" + lidarr: container_name: lidarr image: lscr.io/linuxserver/lidarr:latest - ports: - - 8686:8686 - env_file: - - arr.env - - mb.env + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + - MB_USER={{ arrstack_mb_user }} + - MB_PASS={{ arrstack_mb_pass }} environment: - DOCKER_MODS=linuxserver/mods:universal-docker volumes: - - "./config/lidarr:/config" + - "{{ arrstack_env_dir }}/config/lidarr:/config" - "/var/run/docker.sock:/var/run/docker.sock:ro" - "{{ arrstack_data_dir }}/media/music:/data/media/music" - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" restart: unless-stopped + labels: + caddy: "http://lidarr.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 8686{{ '}}'}}" + readarr: container_name: readarr image: lscr.io/linuxserver/readarr:develop - ports: - - 8787:8787 - env_file: - - arr.env + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} volumes: - - "./config/readarr:/config" + - "{{ arrstack_env_dir }}/config/readarr:/config" - "{{ arrstack_data_dir }}/media/audiobooks:/data/media/audiobooks" - "{{ arrstack_data_dir }}/files/usenet:/data/usenet" - "{{ arrstack_data_dir }}/files/torrent:/data/torrent" restart: unless-stopped + labels: + caddy: "http://readarr.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 8787{{ '}}'}}" + prowlarr: container_name: prowlarr image: lscr.io/linuxserver/prowlarr:develop - env_file: - - arr.env + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} volumes: - - "./config/prowlarr:/config" - ports: - - 9696:9696 + - "{{ arrstack_env_dir }}/config/prowlarr:/config" restart: unless-stopped + labels: + caddy: "http://prowlarr.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 9696{{ '}}'}}" + + beets: + image: lscr.io/linuxserver/beets:latest + container_name: beets + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + - MB_USER={{ arrstack_mb_user }} + - MB_PASS={{ arrstack_mb_pass }} + volumes: + - "{{ arrstack_env_dir }}/config/beets:/config" + - "{{ arrstack_data_dir }}/media/music:/music" + - "{{ arrstack_data_dir }}/files/music-unsorted:/downloads" + restart: unless-stopped + labels: + caddy: "http://prowlarr.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 8337{{ '}}'}}" + sabnzbd: container_name: sabnzbd image: lscr.io/linuxserver/sabnzbd:latest - env_file: - - arr.env + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} volumes: - - "./config/sabnzbd:/config" + - "{{ arrstack_env_dir }}/config/sabnzbd:/config" - "{{ arrstack_data_dir }}/files/usenet:/data/usenet:rw" - ports: - - 8080:8080 restart: unless-stopped + labels: + caddy: "http://usenet.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 8080{{ '}}'}}" + vpn: - image: qmcgaw/gluetun:v3 container_name: vpn + image: qmcgaw/gluetun:v3 + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + - VPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port + - FIREWALL_OUTBOUND_SUBNETS=172.18.0.0/24 + - BLOCK_SURVEILLANCE=on + - VPN_SERVICE_PROVIDER={{ arrstack_vpn_provider }} + - OPENVPN_USER={{ arrstack_vpn_user }} + - OPENVPN_PASSWORD={{ arrstack_vpn_pass }} + - SERVER_REGIONS={{ arrstack_vpn_regions }} + - PORT_FORWARD_ONLY=true + - VPN_PORT_FORWARDING=on + - VPN_PORT_FORWARDING_PROVIDER={{ arrstack_vpn_provider }} + - QBITTORRENT_USER={{ arrstack_qbit_user }} + - QBITTORRENT_PASS={{ arrstack_qbit_pass }} cap_add: - NET_ADMIN devices: - /dev/net/tun:/dev/net/tun - env_file: - - arr.env - - pia.env - environment: - - VPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port - - FIREWALL_OUTBOUND_SUBNETS=172.18.0.0/24 - - BLOCK_SURVEILLANCE=on volumes: - - "./config/gluetun:/gluetun" - ports: - - 8000:8000 # gluetun http control - - 8888:8888 # qBittorrent WebUI + - "{{ arrstack_env_dir }}/config/gluetun:/gluetun" + #ports: # TODO: should this be exposed? + # - 8000:8000 # gluetun http control restart: unless-stopped + labels: + caddy: "http://torrent.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 8888{{ '}}'}}" qbittorrent: image: linuxserver/qbittorrent container_name: qbittorrent - env_file: - - arr.env environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} - WEBUI_PORT=8888 volumes: - - "./config/piaqbit:/config" - - "./config/gluetun:/gluetun" + - "{{ arrstack_env_dir }}/config/piaqbit:/config" + - "{{ arrstack_env_dir }}/config/gluetun:/gluetun" - "{{ arrstack_data_dir }}/files/torrent:/downloads" depends_on: - vpn @@ -120,19 +194,30 @@ services: gluetun-qbittorrent-port-manager: image: patrickaclark/gluetun-qbittorrent-port-manager:latest container_name: qbit-port-manager - env_file: - - arr.env - - pia.env environment: - - QBITTORRENT_SERVER=localhost # IP Address of qbittorrent + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + - WEBUI_PORT=8888 + - QBITTORRENT_SERVER=qbittorrent # IP Address of qbittorrent - QBITTORRENT_PORT=8888 - PORT_FORWARDED=/gluetun/forwarded_port - HTTP_S=http # Select 'http' or 'https' depending on if you use certificates. - - GLUETUN_HOST=localhost # IP or FQDN of gluetun control server + - GLUETUN_HOST=vpn # IP or FQDN of gluetun control server - GLUETUN_PORT=8000 # port of gluetun control server - RECHECK_TIME=60 # number of seconds between checks to gluetun server for port + - VPN_SERVICE_PROVIDER={{ arrstack_vpn_provider }} + - OPENVPN_USER={{ arrstack_vpn_user }} + - OPENVPN_PASSWORD={{ arrstack_vpn_pass }} + - SERVER_REGIONS={{ arrstack_vpn_regions }} + - PORT_FORWARD_ONLY=true + - VPN_PORT_FORWARDING=on + - VPN_PORT_FORWARDING_PROVIDER={{ arrstack_vpn_provider }} + - QBITTORRENT_USER={{ arrstack_qbit_user }} + - QBITTORRENT_PASS={{ arrstack_qbit_pass }} volumes: - - "./config/gluetun:/gluetun" + - "{{ arrstack_env_dir }}/config/gluetun:/gluetun" depends_on: - vpn network_mode: "service:vpn" @@ -143,67 +228,110 @@ services: timeout: 10s start_period: 60s retries: 3 - jellyfin: - image: lscr.io/linuxserver/jellyfin:latest - container_name: jellyfin - env_file: - - arr.env - #environment: - #- JELLYFIN_PublishedServerUrl=192.168.0.5 #optional - volumes: - - ".config/jellyfin:/config" - - "/mnt/ext/data/media/movies:/media/movies" # FIXME: To be changed? - - "{{ arrstack_data_dir }}/media/tv:/media/tv" - - "{{ arrstack_data_dir }}/media/music:/media/music" - ports: - - 8096:8096 - - 7359:7359/udp #optional - network discovery - - 1900:1900/udp #optional - dlna discovery - restart: unless-stopped - audiobookshelf: - container_name: audiobookshelf - image: ghcr.io/advplyr/audiobookshelf:latest - env_file: - - arr.env - ports: - - 13378:80 - volumes: - - "{{ arrstack_data_dir }}/media/audiobooks:/audiobooks" - # - "{{ arrstack_data_dir }}/media/podcasts:/podcasts" # TODO: If integrating podcasts - - ".config/audiobookshelf:/config" - - ".metadata/audiobookshelf:/metadata" - restart: unless-stopped - jellyseerr: - image: fallenbagel/jellyseerr:latest - container_name: jellyseerr - env_file: - - arr.env - ports: - - 5055:5055 - volumes: - - "./config/jellyseerr:/app/config" - restart: unless-stopped - beets: - image: lscr.io/linuxserver/beets:latest - container_name: beets - env_file: - - arr.env - - mb.env - volumes: - - "./config/beets:/config" - - "{{ arrstack_data_dir }}/media/music:/music" - - "{{ arrstack_data_dir }}/files/music-unsorted:/downloads" - ports: - - 8337:8337 - restart: unless-stopped + homarr: image: ghcr.io/ajnart/homarr:latest container_name: homarr + networks: + - caddy volumes: + - {{ arrstack_env_dir }}/config/homarr/configs:/app/data/configs + - {{ arrstack_env_dir }}/config/homarr/icons:/app/public/icons + - {{ arrstack_env_dir }}/config/homarr/data:/data - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration - - ./config/homarr/configs:/app/data/configs - - ./config/homarr/icons:/app/public/icons - - ./config/homarr/data:/data - ports: - - '80:7575' restart: unless-stopped + labels: + caddy: "http://pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 7575{{ '}}'}}" + + jellyseerr: + image: fallenbagel/jellyseerr:latest + container_name: jellyseerr + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + volumes: + - "{{ arrstack_env_dir }}/config/jellyseerr:/app/config" + restart: unless-stopped + labels: + caddy: "http://get.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 5055{{ '}}'}}" + + audiobookshelf: + container_name: audiobookshelf + image: ghcr.io/advplyr/audiobookshelf:latest + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + volumes: + - "{{ arrstack_env_dir }}/config/audiobookshelf:/config" + - "{{ arrstack_env_dir }}/data/audiobookshelf:/metadata" + - "{{ arrstack_data_dir }}/media/audiobooks:/audiobooks" + # - "{{ arrstack_data_dir }}/media/podcasts:/podcasts" # TODO: If integrating podcasts + restart: unless-stopped + labels: + caddy: "http://books.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 80{{ '}}'}}" + + jellyfin: + image: lscr.io/linuxserver/jellyfin:latest + container_name: jellyfin + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + devices: + - /dev/dri:/dev/dri + #environment: + #- JELLYFIN_PublishedServerUrl=192.168.0.5 #optional + volumes: + - "{{ arrstack_env_dir }}/config/jellyfin:/config" + - "{{ arrstack_env_dir }}/data/jellyfin:/config/data" + - "/mnt/ext/data/media/movies:/media/movies" # FIXME: To be changed? + - "{{ arrstack_data_dir }}/media/tv:/media/tv" + - "{{ arrstack_data_dir }}/media/music:/media/music" + ports: # FIXME: how to enable discovery behind proxies? + - 7359:7359/udp #optional - network discovery + - 1900:1900/udp #optional - dlna discovery + restart: unless-stopped + labels: + caddy: "http://media.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 8096{{ '}}'}}" + + gonic: + image: sentriz/gonic:latest + networks: + - caddy + environment: + - PUID={{ arrstack_puid }} + - PGID={{ arrstack_pgid }} + - TZ={{ arrstack_tz }} + - UMASK_SET={{ arrstack_umask_set }} + volumes: + - "{{ arrstack_env_dir }}/data/gonic:/data" + - "{{ arrstack_env_dir }}/data/gonic_playlists:/playlists" + - "/srv/media/music:/music:ro" + - "/srv/media/podcasts:/podcasts" + #- /path/to/cache:/cache # transcode / covers / etc cache dir + labels: + caddy: "http://music.pichi.berlin" + caddy.reverse_proxy: "{{ '{{' }}upstreams 80{{ '}}'}}" + + +networks: + caddy: + external: true + +volumes: + caddy_data: {} From 6374fa8effca6da5df930c05926d3218853f0850 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 15 Jul 2025 08:16:48 +0200 Subject: [PATCH 14/14] Keep sensitive vars in vault --- ansible/ansible.cfg | 2 ++ ansible/group_vars/instance_system/vars.yaml | 1 + ansible/group_vars/instance_system/vault.yaml | 21 +++++++++++++++++++ ansible/roles/arr/defaults/main.yml | 2 +- 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 ansible/group_vars/instance_system/vars.yaml create mode 100644 ansible/group_vars/instance_system/vault.yaml diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index c491d26..168509f 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -1,3 +1,5 @@ [defaults] remote_tmp = /tmp inventory = inventory + +vault_password_file = vaultpass diff --git a/ansible/group_vars/instance_system/vars.yaml b/ansible/group_vars/instance_system/vars.yaml new file mode 100644 index 0000000..9aeda4e --- /dev/null +++ b/ansible/group_vars/instance_system/vars.yaml @@ -0,0 +1 @@ +arrstack_tz: Europe/Berlin diff --git a/ansible/group_vars/instance_system/vault.yaml b/ansible/group_vars/instance_system/vault.yaml new file mode 100644 index 0000000..dbf15e9 --- /dev/null +++ b/ansible/group_vars/instance_system/vault.yaml @@ -0,0 +1,21 @@ +$ANSIBLE_VAULT;1.1;AES256 +38663563663066323465636561656239653630366234366662333834646137386466353561666139 +6263613364316362663863366431663963646461656564360a366332326435386134356233616632 +38336136386662383439613830373933633566393836613932653564633938656130663764313961 +3532373338643762390a656266386633313037316638306435653830333430656430396138313032 +36396562393534366665376562343361386634343262326238393535326431333637653836636436 +33313833333565623239306232363935656534643030653961636462383164353230373465393238 +64393461343533616536353865623835633065656535316262356365373663616263373637396534 +65376266643538663437306237633436656130646430333036336139336231353062626462646135 +62326536323362313136356435656364333562363335396237613331626539356139353830353961 +33346362653163633063633531663539613630636461323564363633626166353139623030633962 +36346331613839373036656231663436653538336336376166373231323037333937643530626161 +63323364643736313632323939623833343338393038623939356162653932323265353461303133 +32633266616664393739636633356562386430316134353564396664643431623963316136306666 +30353566643337306439316132313535303235346430346631303933356465383039663065333163 +65323931356165366235383463373738326134303835393738633331336431333563376431313364 +38376138386530313764616239653462656133626633613532303937653435663932633039313338 +39373965313338623162306632303165323863376430316461336336393630393766383339393334 +63353530393164376165313238656137383936616232303130373337326165646231613765356438 +39343265666638396631386132373734323037313339633837313961613938386561663733333134 +65633338366361646332 diff --git a/ansible/roles/arr/defaults/main.yml b/ansible/roles/arr/defaults/main.yml index 47ea10a..e68c8ef 100644 --- a/ansible/roles/arr/defaults/main.yml +++ b/ansible/roles/arr/defaults/main.yml @@ -7,7 +7,7 @@ arrstack_data_dir_create: true arrstack_puid: 1000 arrstack_pgid: 100 -arrstack_tz: Europe/Berlin +arrstack_tz: NorthAmerica/Chicago arrstack_umask_set: 022 # arrstack_mb_user: Musicbrainz-user