diff --git a/.gitignore b/.gitignore index 383de38..b784e05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ vaultpass - -/.ansible -/temp/ diff --git a/README.md b/README.md index 462261a..e64469b 100644 --- a/README.md +++ b/README.md @@ -17,60 +17,3 @@ 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. - -## 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:... diff --git a/ansible.cfg b/ansible.cfg index 8734d30..168509f 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -1,4 +1,5 @@ [defaults] remote_tmp = /tmp inventory = inventory -roles_path = .ansible/roles:roles + +vault_password_file = vaultpass diff --git a/justfile b/justfile deleted file mode 100644 index 5afc29f..0000000 --- a/justfile +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 22c5e84..0000000 --- a/requirements.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Install and pre-configure incus -- src: gliech.incus -# install and set up docker -- src: geerlingguy.docker diff --git a/roles/filesystem/README.md b/roles/filesystem/README.md deleted file mode 100644 index c80df74..0000000 --- a/roles/filesystem/README.md +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index 0c5589e..0000000 --- a/roles/filesystem/defaults/main.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -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 deleted file mode 100644 index b00e076..0000000 --- a/roles/filesystem/handlers/main.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- - -- 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 deleted file mode 100644 index 0e85f23..0000000 --- a/roles/filesystem/tasks/main.yaml +++ /dev/null @@ -1,30 +0,0 @@ ---- -- 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 deleted file mode 100644 index 664bc9a..0000000 --- a/roles/filesystem/vars/main.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -# vars file for btrfs diff --git a/roles/incus-install/tasks/bootstrap.yaml b/roles/incus-install/tasks/bootstrap.yaml new file mode 100644 index 0000000..c6e1dfa --- /dev/null +++ b/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/roles/incus-install/tasks/install.yaml b/roles/incus-install/tasks/install.yaml deleted file mode 100644 index 2b5f4ab..0000000 --- a/roles/incus-install/tasks/install.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# 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 6623f4f..c465a5f 100644 --- a/roles/incus-install/tasks/main.yaml +++ b/roles/incus-install/tasks/main.yaml @@ -1,9 +1,7 @@ --- -## for bookworm only -- name: "Add incus repository to bookworm system" +- name: "Add incus repository to system" ansible.builtin.include_tasks: add-repo.yaml - when: ansible_distribution == "Debian" and ansible_distribution_release == "bookworm" -# TODO: there might be remaining issues on other OSes like centos, etc -- name: Install incus - ansible.builtin.include_tasks: install.yaml + # TODO: Should presumably be split +- name: "Install and bootstrap incus" + ansible.builtin.include_tasks: bootstrap.yaml diff --git a/roles/system/defaults/main.yaml b/roles/system/defaults/main.yaml deleted file mode 100644 index 7100009..0000000 --- a/roles/system/defaults/main.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- - -system_timezone: "Europe/Berlin" -system_users: - - name: marty - groups: - - marty - - data - - incus-admin - authorized_keys: - - "{{ lookup('file', '~/.ssh/keys/bob.pub') }}" - - name: data - groups: - - data - create_home: false - shell: /sbin/nologin diff --git a/roles/incus-install/files/incus.servers.tpl b/roles/system/files/incus.servers.tpl similarity index 100% rename from roles/incus-install/files/incus.servers.tpl rename to roles/system/files/incus.servers.tpl diff --git a/roles/incus-install/files/incus.sources.tpl b/roles/system/files/incus.sources.tpl similarity index 100% rename from roles/incus-install/files/incus.sources.tpl rename to roles/system/files/incus.sources.tpl diff --git a/roles/incus-install/files/zabbly-key.asc b/roles/system/files/zabbly-key.asc similarity index 100% rename from roles/incus-install/files/zabbly-key.asc rename to roles/system/files/zabbly-key.asc diff --git a/roles/system/tasks/main.yaml b/roles/system/tasks/main.yaml index 4ce6c5a..068c2e0 100644 --- a/roles/system/tasks/main.yaml +++ b/roles/system/tasks/main.yaml @@ -36,38 +36,3 @@ - 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: "{{ item.name }}" - state: present - key: "{{ item.authorized_keys }}" - loop: "{{ system_users }}" - when: system_users is defined and item.authorized_keys is defined - tags: - - ssh - become: true diff --git a/site.yaml b/site.yaml index 0f89754..ce1b5c6 100644 --- a/site.yaml +++ b/site.yaml @@ -15,30 +15,16 @@ register: pythoncheck - name: install debian python ansible.builtin.raw: apt-get update && apt-get install python3 -y - 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 + when: pythoncheck.rc == 127 - 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 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