From 66d10328698b36555c75053e5e7da89e7e2221a7 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Sat, 15 Nov 2025 10:05:00 +0100 Subject: [PATCH 01/14] ref: Create scratch dir for not to be commited items --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b784e05..0ee0457 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ vaultpass + +/temp/ From 54b84047437f18ece95bcb3ea27d08c9b42d697e Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 15:16:35 +0100 Subject: [PATCH 02/14] fix: Move incus template files into correct role Moved from system role where they used to be required into the (currently disabled) incus installation role. --- roles/{system => incus-install}/files/incus.servers.tpl | 0 roles/{system => incus-install}/files/incus.sources.tpl | 0 roles/{system => incus-install}/files/zabbly-key.asc | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename roles/{system => incus-install}/files/incus.servers.tpl (100%) rename roles/{system => incus-install}/files/incus.sources.tpl (100%) rename roles/{system => incus-install}/files/zabbly-key.asc (100%) diff --git a/roles/system/files/incus.servers.tpl b/roles/incus-install/files/incus.servers.tpl similarity index 100% rename from roles/system/files/incus.servers.tpl rename to roles/incus-install/files/incus.servers.tpl diff --git a/roles/system/files/incus.sources.tpl b/roles/incus-install/files/incus.sources.tpl similarity index 100% rename from roles/system/files/incus.sources.tpl rename to roles/incus-install/files/incus.sources.tpl diff --git a/roles/system/files/zabbly-key.asc b/roles/incus-install/files/zabbly-key.asc similarity index 100% rename from roles/system/files/zabbly-key.asc rename to roles/incus-install/files/zabbly-key.asc From e6194e35bfae5d0a4b3752461952684a18dd98aa Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 15:22:15 +0100 Subject: [PATCH 03/14] docs: Add more stacks to readme --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index e64469b..69cb73b 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,18 @@ and media management applications jellyfin and audiobookshelf. Media can be requested through Jellyseerr. Served through homarr personal dashboard. + +## Paperless stack + +Hosts all my personal documents. This is an important stack which should be backed up accordingly. + +## Grocy stack + +Was an experimental stack which I may have used in my home for shopping lists, ingredient tracking, +and more. + +After some consideration and experimentation, for the moment, I have decided against using grocy: +it provides comprehensive tracking but also requires comprehensive use to get the most out of it. + +I get the feeling a badly implemented/maintained grocy setup is _worse_ than a simpler task-list and +e.g. Recipe KanBan board approach. From 0de79fc1d2fba0bd12d272b3b0ced17825536e1d Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 16:57:39 +0100 Subject: [PATCH 04/14] ref: Remove vault-password static file from repo Instead of having the file statically (and plain-text) in the repo itself, we simply query `pass` for it instead. Slightly cumbersome syntax since ansible (afaik) does not allow a similar easy variable-enabled lookup as for become passwords, so we also whipped it into a justfile to not have to type it each time. The command line uses cat to receive the password as a 'file' on stdin. --- ansible.cfg | 2 -- justfile | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 justfile diff --git a/ansible.cfg b/ansible.cfg index 168509f..c491d26 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,5 +1,3 @@ [defaults] remote_tmp = /tmp inventory = inventory - -vault_password_file = vaultpass diff --git a/justfile b/justfile new file mode 100644 index 0000000..e37dd2a --- /dev/null +++ b/justfile @@ -0,0 +1,2 @@ +deploy: + pass show hosting/ansible/bob/vault-password | ansible-playbook --vault-password-file=/bin/cat site.yaml From 5257525c7e9b3c8cfa575eb67b4d6b817ab0ba00 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 16:57:39 +0100 Subject: [PATCH 05/14] ref: Add local ansible dir to gitignore Will contain local ansible roles and perhaps more, set in ansible.cfg. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0ee0457..383de38 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ vaultpass +/.ansible /temp/ From 5f737ab4b537a1338445bd9414282ae01fcf3bb0 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 19:25:51 +0100 Subject: [PATCH 06/14] feat: Set up ansible role install task for just --- justfile | 3 +++ requirements.yaml | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 requirements.yaml diff --git a/justfile b/justfile index e37dd2a..5afc29f 100644 --- a/justfile +++ b/justfile @@ -1,2 +1,5 @@ +install: + ansible-galaxy install -r requirements.yaml + deploy: pass show hosting/ansible/bob/vault-password | ansible-playbook --vault-password-file=/bin/cat site.yaml diff --git a/requirements.yaml b/requirements.yaml new file mode 100644 index 0000000..22c5e84 --- /dev/null +++ b/requirements.yaml @@ -0,0 +1,4 @@ +# Install and pre-configure incus +- src: gliech.incus +# install and set up docker +- src: geerlingguy.docker From d8ed04f4d13389d98550daf2770b031bf771d5e7 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 16:57:39 +0100 Subject: [PATCH 07/14] ref: Add galaxy installed ansible roles into local directory --- ansible.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/ansible.cfg b/ansible.cfg index c491d26..8734d30 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,3 +1,4 @@ [defaults] remote_tmp = /tmp inventory = inventory +roles_path = .ansible/roles:roles From a217d65640542fadda3c7e9c73b40b21f594f7ed Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 16:57:39 +0100 Subject: [PATCH 08/14] feat: Update incus installation role Now uses simple external ansible galaxy role, and should install incus from a pre-fixed seed. --- roles/incus-install/tasks/bootstrap.yaml | 220 ----------------------- roles/incus-install/tasks/install.yaml | 70 ++++++++ roles/incus-install/tasks/main.yaml | 10 +- site.yaml | 5 + 4 files changed, 81 insertions(+), 224 deletions(-) delete mode 100644 roles/incus-install/tasks/bootstrap.yaml create mode 100644 roles/incus-install/tasks/install.yaml diff --git a/roles/incus-install/tasks/bootstrap.yaml b/roles/incus-install/tasks/bootstrap.yaml deleted file mode 100644 index c6e1dfa..0000000 --- a/roles/incus-install/tasks/bootstrap.yaml +++ /dev/null @@ -1,220 +0,0 @@ ---- -- 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/roles/incus-install/tasks/install.yaml b/roles/incus-install/tasks/install.yaml new file mode 100644 index 0000000..2b5f4ab --- /dev/null +++ b/roles/incus-install/tasks/install.yaml @@ -0,0 +1,70 @@ +# required by gliech.incus role +- name: Ensure lxc conf directory exists + ansible.builtin.file: + state: directory + path: /etc/lxc + become: true + +- name: Install incus + ansible.builtin.import_role: + name: gliech.incus + vars: + incus_config: + config: + core.https_address: "[::]:8443" + networks: + - config: + ipv4.address: 10.172.89.1/24 + ipv4.firewall: "true" + ipv4.nat: "true" + ipv6.address: fd42:c9d2:6e9f:be57::1/64 + ipv6.nat: "true" + description: "" + name: incusbr0 + type: bridge + project: default + storage_pools: + - config: + source: /var/lib/incus/storage-pools/default + volatile.initial_source: /var/lib/incus/storage-pools/default + description: "" + name: default + driver: btrfs + - config: + size: 50GiB + source: /var/lib/incus/disks/docker_store.img + description: "" + name: docker_store + driver: btrfs + storage_volumes: [] + profiles: + - config: {} + description: Default Incus profile + devices: + eth0: + name: eth0 + network: incusbr0 + type: nic + root: + path: / + pool: default + type: disk + name: default + project: "" + projects: + - config: + features.images: "true" + features.networks: "true" + features.networks.zones: "true" + features.profiles: "true" + features.storage.buckets: "true" + features.storage.volumes: "true" + restricted: "false" + description: NAS + name: default + certificates: [] + cluster_groups: [] +# # TODO: Should presumably be split +# - name: "Install and bootstrap incus" +# ansible.builtin.include_tasks: bootstrap.yaml +# when: ansible_distribution == "Debian" and ansible_distribution_release == "bookworm" diff --git a/roles/incus-install/tasks/main.yaml b/roles/incus-install/tasks/main.yaml index c465a5f..6623f4f 100644 --- a/roles/incus-install/tasks/main.yaml +++ b/roles/incus-install/tasks/main.yaml @@ -1,7 +1,9 @@ --- -- name: "Add incus repository to system" +## for bookworm only +- name: "Add incus repository to bookworm system" ansible.builtin.include_tasks: add-repo.yaml + when: ansible_distribution == "Debian" and ansible_distribution_release == "bookworm" - # TODO: Should presumably be split -- name: "Install and bootstrap incus" - ansible.builtin.include_tasks: bootstrap.yaml +# TODO: there might be remaining issues on other OSes like centos, etc +- name: Install incus + ansible.builtin.include_tasks: install.yaml diff --git a/site.yaml b/site.yaml index ce1b5c6..1e43b4d 100644 --- a/site.yaml +++ b/site.yaml @@ -25,6 +25,11 @@ name: system tags: system + - name: Set up incus + ansible.builtin.import_role: + name: incus-install + tags: incus + - name: Set up nfs shares ansible.builtin.import_role: name: nfs From bb9de502ce0354ad20b4e92508046773c2a8be4d Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Nov 2025 21:54:43 +0100 Subject: [PATCH 09/14] feat: Set up filesystems Automatically set up btrfs root and data filesystem, as well as external HDD. This automation change assumes a layout exactly as in current bob to function by default, can be changed to any btrfs layout with the `btrfs_mounts` configuration option, however. --- roles/filesystem/README.md | 38 +++++++++++++++++++++++++++++ roles/filesystem/defaults/main.yaml | 30 +++++++++++++++++++++++ roles/filesystem/handlers/main.yaml | 7 ++++++ roles/filesystem/tasks/main.yaml | 30 +++++++++++++++++++++++ roles/filesystem/vars/main.yaml | 2 ++ site.yaml | 5 ++++ 6 files changed, 112 insertions(+) create mode 100644 roles/filesystem/README.md create mode 100644 roles/filesystem/defaults/main.yaml create mode 100644 roles/filesystem/handlers/main.yaml create mode 100644 roles/filesystem/tasks/main.yaml create mode 100644 roles/filesystem/vars/main.yaml diff --git a/roles/filesystem/README.md b/roles/filesystem/README.md new file mode 100644 index 0000000..c80df74 --- /dev/null +++ b/roles/filesystem/README.md @@ -0,0 +1,38 @@ +filesystem +========= + +Mounts the filesystem(s) required for bob. +Focused on correct `btrfs` layout and mounting, +but also mounts an external `ext4` HDD by default. + +Requirements +------------ + +This role requires a btrfs filesystem _existing_ on _any device_ that is targeted with the role (using the 'btrfs_mounts') configuration option. +Optionally, an external HDD is required if the mount toggle is true. + +Role Variables +-------------- + +`btrfs_mounts` can be used to set up the various (top-level) btrfs subvolumes, and their later mount points in the system. +Define an entry by giving the `uuid` of the targeted btrfs filesystem (or device), give the name of the `subvol` you intend to give, and define the `path` where it should ultimately be mounted by `fstab`. +You can additionally provide some `opts` which are given to `fstab` for the mount process as well. + +Example: + +```yaml +btrfs_mounts: + - path: "/home" + subvol: "@home" + uuid: "3204f33f-5fa7-4c11-bdd8-979c539fce91" + opts: "defaults,ssd,noatime,compress=zstd:3,space_cache=v2" + + - path: "/srv/media" + subvol: "@media" + uuid: "2256bd23-7751-486a-bfc4-b216a6b0c4f4" + opts: "defaults,noatime,compress=zstd:3,space_cache=v2" +``` + +--- + +`should_mount_external_hdd` can be toggled to automatically mount a USB-connected HDD on boot, or not. diff --git a/roles/filesystem/defaults/main.yaml b/roles/filesystem/defaults/main.yaml new file mode 100644 index 0000000..0c5589e --- /dev/null +++ b/roles/filesystem/defaults/main.yaml @@ -0,0 +1,30 @@ +--- +should_mount_external_hdd: true +should_reboot_machine: false + +btrfs_mounts: + - path: "/" + subvol: "@rootfs" + uuid: "3204f33f-5fa7-4c11-bdd8-979c539fce91" + opts: "defaults,ssd,noatime,compress=zstd:3,space_cache=v2" + - path: "/home" + subvol: "@home" + uuid: "3204f33f-5fa7-4c11-bdd8-979c539fce91" + opts: "defaults,ssd,noatime,compress=zstd:3,space_cache=v2" + + - path: "/srv/media" + subvol: "@media" + uuid: "2256bd23-7751-486a-bfc4-b216a6b0c4f4" + opts: "defaults,noatime,compress=zstd:3,space_cache=v2" + - path: "/srv/files" + subvol: "@files" + uuid: "2256bd23-7751-486a-bfc4-b216a6b0c4f4" + opts: "defaults,noatime,compress=zstd:3,space_cache=v2" + - path: "/srv/documents" + subvol: "@documents" + uuid: "2256bd23-7751-486a-bfc4-b216a6b0c4f4" + opts: "defaults,noatime,compress=zstd:3,space_cache=v2" + - path: "/srv/wolf" + subvol: "@wolf" + uuid: "2256bd23-7751-486a-bfc4-b216a6b0c4f4" + opts: "defaults,noatime,compress=zstd:1,space_cache=v2" diff --git a/roles/filesystem/handlers/main.yaml b/roles/filesystem/handlers/main.yaml new file mode 100644 index 0000000..b00e076 --- /dev/null +++ b/roles/filesystem/handlers/main.yaml @@ -0,0 +1,7 @@ +--- + +- name: Reboot machine + ansible.builtin.reboot: + connect_timeout: 600 + post_reboot_delay: 10 + when: "should_reboot_machine" diff --git a/roles/filesystem/tasks/main.yaml b/roles/filesystem/tasks/main.yaml new file mode 100644 index 0000000..0e85f23 --- /dev/null +++ b/roles/filesystem/tasks/main.yaml @@ -0,0 +1,30 @@ +--- +- name: Ensure btrfs ROOT layout + community.general.btrfs_subvolume: + name: "/{{ item.subvol }}" + # filesystem_device: /dev/sdb1 + # fileystem_label: btrfs-root # only 1 of the 3 required + filesystem_uuid: "{{ item.uuid }}" + loop: "{{ btrfs_mounts }}" + become: true + +- name: Ensure fstab contains btrfs mount entries + ansible.posix.mount: + path: "{{ item.path }}" + src: "UUID={{ item.uuid }}" + fstype: btrfs + opts: "{{ item.opts }},subvol={{ item.subvol }}" + state: present + loop: "{{ btrfs_mounts }}" + become: true + notify: Reboot machine + +- name: Ensure external HDD is mounted + ansible.posix.mount: + path: /mnt/ext + src: "UUID=01b221f2-83a5-49e4-bdef-ee9ee9ac5310" + fstype: ext4 + opts: "noatime" + state: present + become: true + when: "should_mount_external_hdd" diff --git a/roles/filesystem/vars/main.yaml b/roles/filesystem/vars/main.yaml new file mode 100644 index 0000000..664bc9a --- /dev/null +++ b/roles/filesystem/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# vars file for btrfs diff --git a/site.yaml b/site.yaml index 1e43b4d..7cdaf6c 100644 --- a/site.yaml +++ b/site.yaml @@ -20,6 +20,11 @@ - name: Prepare incus server host hosts: host_system tasks: + - name: Prepare host filesystems + ansible.builtin.import_role: + name: filesystem + tags: filesystem + - name: Prepare system ansible.builtin.import_role: name: system From 8019fa9276aa4bd3d944ed6f3250e839c2dc2dae Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 19 Nov 2025 13:57:19 +0100 Subject: [PATCH 10/14] docs: Add organization roadmap to README --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 69cb73b..462261a 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,45 @@ it provides comprehensive tracking but also requires comprehensive use to get th I get the feeling a badly implemented/maintained grocy setup is _worse_ than a simpler task-list and e.g. Recipe KanBan board approach. + +## Thoughts on organization + +. + ansible + roles + system + infrastructure -> calls tofu role + arr + paperless + ... + + tofu + incus_machines + incus_networks? + incus_storage? + +### Production IaC + +- ansible: + host_roles: + system + filesystem +- terraform: + infrastructure (tofu) +- ansible: + instance_roles: + caddy + arr + paperless + +### Testing + +- terraform? ansible? + - create 'host' VM + - ensure connection to host vm as part of host group +- ansible: + host_roles: ... +- tf: + infra... +- ansible: + instance_roles:... From b493485b9034920c379a4c92aef39432c0c2c414 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 19 Nov 2025 14:35:30 +0100 Subject: [PATCH 11/14] feat: Add authorized ssh keys to host --- roles/system/defaults/main.yaml | 4 ++++ roles/system/tasks/main.yaml | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 roles/system/defaults/main.yaml diff --git a/roles/system/defaults/main.yaml b/roles/system/defaults/main.yaml new file mode 100644 index 0000000..e60978c --- /dev/null +++ b/roles/system/defaults/main.yaml @@ -0,0 +1,4 @@ +--- + +system_authorized_keys: + - "{{ lookup('file', '~/.ssh/keys/bob.pub') }}" diff --git a/roles/system/tasks/main.yaml b/roles/system/tasks/main.yaml index 068c2e0..0263ec1 100644 --- a/roles/system/tasks/main.yaml +++ b/roles/system/tasks/main.yaml @@ -36,3 +36,12 @@ - packages become: true +- name: Add authorized SSH keys + ansible.posix.authorized_key: + user: marty # FIXME: don't hardoce user + state: present + key: "{{ item }}" + loop: "{{ system_authorized_keys }}" + tags: + - ssh + become: true From 2fc23d9774f05830f23fffb1eea862d7ee1fb0f0 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 19 Nov 2025 14:35:30 +0100 Subject: [PATCH 12/14] feat: Set up timezone and users and groups on system host --- roles/system/defaults/main.yaml | 13 +++++++++++++ roles/system/tasks/main.yaml | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/roles/system/defaults/main.yaml b/roles/system/defaults/main.yaml index e60978c..11712bb 100644 --- a/roles/system/defaults/main.yaml +++ b/roles/system/defaults/main.yaml @@ -1,4 +1,17 @@ --- +system_timezone: "Europe/Berlin" +system_users: + - name: marty + groups: + - marty + - data + - incus-admin + - name: data + groups: + - data + create_home: false + shell: /sbin/nologin + system_authorized_keys: - "{{ lookup('file', '~/.ssh/keys/bob.pub') }}" diff --git a/roles/system/tasks/main.yaml b/roles/system/tasks/main.yaml index 0263ec1..d2c4f42 100644 --- a/roles/system/tasks/main.yaml +++ b/roles/system/tasks/main.yaml @@ -36,6 +36,31 @@ - packages become: true +- name: Set correct timezone + community.general.timezone: + name: "{{ system_timezone }}" + when: "system_timezone" + become: true + +- name: Create necessary groups + ansible.builtin.group: + name: "{{ item }}" + state: present + loop: "{{ system_users | map(attribute='groups') | flatten | unique }}" + when: "system_users" + become: true + +- name: Set up system users + ansible.builtin.user: + name: "{{ item.name }}" + groups: "{{ item.groups }}" + append: "{{ item.append | default(true) }}" + create_home: "{{ item.create_home | default(false) }}" + shell: "{{ item.shell | default('/bin/bash') }}" + loop: "{{ system_users }}" + when: "system_users" + become: true + - name: Add authorized SSH keys ansible.posix.authorized_key: user: marty # FIXME: don't hardoce user From 66ce16ce557cdcb15af9e2ea05611b7c681321a6 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 19 Nov 2025 14:40:00 +0100 Subject: [PATCH 13/14] fix: Remove bootstrap python setup from check mode Since we can't look into the variable `pythoncheck.rc`, as it doesn't exist, we skip the `when` check for checkmode -- its installation cannot run under any circumstance in the mode anyways. --- site.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site.yaml b/site.yaml index 7cdaf6c..0f89754 100644 --- a/site.yaml +++ b/site.yaml @@ -15,7 +15,11 @@ register: pythoncheck - name: install debian python ansible.builtin.raw: apt-get update && apt-get install python3 -y - when: pythoncheck.rc == 127 + when: not ansible_check_mode and pythoncheck.rc == 127 + - name: pretend installing debian python for check mode + ansible.builtin.debug: + msg: Pretending to install python... + when: ansible_check_mode - name: Prepare incus server host hosts: host_system From 6e3023205720738ca8df393ccba6a85b1ffb5b93 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 19 Nov 2025 14:40:00 +0100 Subject: [PATCH 14/14] ref: Install authorized keys per user Instead of installing authorized keys globally (same for everybody), we pass in the authorized_keys variable per user, and thus the installation also takes place per user. This makes much more sense and works with minimal refactoring. --- roles/system/defaults/main.yaml | 5 ++--- roles/system/tasks/main.yaml | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/roles/system/defaults/main.yaml b/roles/system/defaults/main.yaml index 11712bb..7100009 100644 --- a/roles/system/defaults/main.yaml +++ b/roles/system/defaults/main.yaml @@ -7,11 +7,10 @@ system_users: - marty - data - incus-admin + authorized_keys: + - "{{ lookup('file', '~/.ssh/keys/bob.pub') }}" - name: data groups: - data create_home: false shell: /sbin/nologin - -system_authorized_keys: - - "{{ lookup('file', '~/.ssh/keys/bob.pub') }}" diff --git a/roles/system/tasks/main.yaml b/roles/system/tasks/main.yaml index d2c4f42..4ce6c5a 100644 --- a/roles/system/tasks/main.yaml +++ b/roles/system/tasks/main.yaml @@ -63,10 +63,11 @@ - name: Add authorized SSH keys ansible.posix.authorized_key: - user: marty # FIXME: don't hardoce user + user: "{{ item.name }}" state: present - key: "{{ item }}" - loop: "{{ system_authorized_keys }}" + key: "{{ item.authorized_keys }}" + loop: "{{ system_users }}" + when: system_users is defined and item.authorized_keys is defined tags: - ssh become: true