mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
ansible: add K3s datastore export/import, improve iSCSI handling, and migrate merlin to MariaDB primary
- Added tasks and playbooks for K3s datastore export/import using MariaDB Tools role, with associated tests and defaults. - Enhanced iSCSI role to support mkfs-once logic and safer re-initialization of block storage. - Migrated iSCSI-backed Rancher data from retropie to merlin.prole.org. - Updated k3s roles/playbooks to relax Rancher storage preflight checks, supporting PARTUUID-based mounts. - Adjusted Samba AD NetBIOS name derivation to use uppercase short hostname by default. - Incremented prole DB version to 104, updated generated prole.cfg, inventory, and recovery manifest templates.
This commit is contained in:
parent
cf6d79f505
commit
d2295f4c2b
@ -19,3 +19,8 @@ become_ask_pass = False
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = True
|
||||
|
||||
# Use a repo-local known_hosts file and accept new keys non-interactively.
|
||||
# This prevents unattended runs from failing when inventory uses IPs via `ansible_host`
|
||||
# and the key is not yet present in the user's `~/.ssh/known_hosts`.
|
||||
ssh_common_args = -o UserKnownHostsFile=.ansible/known_hosts -o StrictHostKeyChecking=accept-new
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-03-05 21:07:28
|
||||
; Generated by install.py on 2026-03-06 00:36:32
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -110,7 +110,7 @@ ANSIBLE_DOMAIN = prole.org
|
||||
ANSIBLE_INFRASTRUCTURE = ${PROLE_HOME}/infrastructure
|
||||
ANSIBLE_INVENTORY = ${PROLE_HOME}/infrastructure/inventory
|
||||
ANSIBLE_REALM = PROLE.ORG
|
||||
ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_hosts":["myrddin.prole.org","pi.prole.org","retropie.prole.org","merlin.prole.org"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"]},"hosts":{"myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":["merlin.prole.org"]}
|
||||
ANSIBLE_TOPOLOGY = {"domain":"prole.org","realm":"PROLE.ORG","internal_records":{"aventage.prole.org":"10.0.0.206","fairyland.prole.org":"10.0.0.208","loghost.prole.org":"10.0.0.3","merlin.prole.org":"10.0.0.3","morana.prole.org":"10.0.0.66","morgoth.prole.org":"10.0.0.204","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207","synology.prole.org":"10.0.0.203","zinfandel.prole.org":"10.0.0.205"},"ad_dc":{"host":"myrddin.prole.org","ip":"10.0.0.3"},"k3s":{"server_url":"https://myrddin.prole.org:6443","server_host":"myrddin.prole.org","token_present":true},"groups":{"iscsi":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org"],"pihole":["pi.prole.org","raspberry.prole.org"],"ad_dc":["myrddin.prole.org"],"k3s_hosts":["myrddin.prole.org","pi.prole.org","retropie.prole.org","merlin.prole.org"],"linux_hosts":["pi.prole.org","raspberry.prole.org","myrddin.prole.org","retropie.prole.org","merlin.prole.org"],"mariadb_primary":["merlin.prole.org"],"mariadb_replica":["raspberry.prole.org"],"mariadb:children":["mariadb_primary","mariadb_replica"],"merlin_bootstrap":["merlin"]},"hosts":{"merlin":"10.0.0.3","merlin.prole.org":"10.0.0.3","myrddin.prole.org":"10.0.0.3","pi.prole.org":"10.0.0.5","raspberry.prole.org":"10.0.0.4","retropie.prole.org":"10.0.0.207"},"unmapped_hosts":["mariadb_primary","mariadb_replica"]}
|
||||
KDC_ANSIBLE_DETECTED = 10.0.0.3
|
||||
KDC_AUTO_DETECTED = 10.0.0.3
|
||||
KERBEROS_AUTO_ENABLED = True
|
||||
@ -177,7 +177,7 @@ LOCAL_REGISTRY = localhost:5000
|
||||
LOCAL_REGISTRY_INTERNAL = k3d-prole-registry.localhost:5000
|
||||
|
||||
[Initialization Scripts]
|
||||
STATUS = Attempted
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Deployment]
|
||||
MODE = k3s
|
||||
|
||||
@ -36,6 +36,11 @@ prole_dns_records:
|
||||
value: 73.15.20.166
|
||||
ttl: 300
|
||||
|
||||
- name: merlin
|
||||
type: A
|
||||
value: 10.0.0.3
|
||||
ttl: 300
|
||||
|
||||
- name: morana
|
||||
type: A
|
||||
value: 10.0.0.66
|
||||
@ -134,6 +139,8 @@ prole_internal_a_records:
|
||||
ipv4: 10.0.0.208
|
||||
- fqdn: loghost.prole.org
|
||||
ipv4: 10.0.0.3
|
||||
- fqdn: merlin.prole.org
|
||||
ipv4: 10.0.0.3
|
||||
- fqdn: morana.prole.org
|
||||
ipv4: 10.0.0.66
|
||||
- fqdn: morgoth.prole.org
|
||||
|
||||
@ -8,9 +8,4 @@ k3s_kubeconfig_group: kubeadm
|
||||
k3s_kubeconfig_users:
|
||||
- pi
|
||||
|
||||
# storage
|
||||
k3s_rancher_mountpoint: /var/lib/rancher
|
||||
k3s_rancher_fs_type: ext4
|
||||
k3s_rancher_mount_opts: "_netdev,noatime"
|
||||
k3s_require_rancher_mount: false
|
||||
k3s_data_dir: /var/lib/rancher/k3s
|
||||
|
||||
@ -31,8 +31,18 @@ k3s_node_labels:
|
||||
- "prole.org/role=control"
|
||||
|
||||
# No iSCSI mounts on merlin
|
||||
iscsi_targets: []
|
||||
iscsi_absent_mounts: []
|
||||
iscsi_portal: 10.0.0.203
|
||||
|
||||
# Do not require /var/lib/rancher to be a separate mount on this host.
|
||||
k3s_require_rancher_mount: false
|
||||
iscsi_targets:
|
||||
# PROLE-K3S-1 (moved from retropie)
|
||||
- iqn: "iqn.2000-01.com.synology:synology.Target-16.292d45194a1"
|
||||
chap_user: "prole"
|
||||
chap_password: "{{ vault_iscsi_prole_password }}"
|
||||
mounts:
|
||||
- path: /var/lib/rancher
|
||||
fstype: ext4
|
||||
opts: "_netdev,noatime"
|
||||
# Use PARTUUID (stable across mkfs) because we intentionally re-initialize the filesystem.
|
||||
src: "PARTUUID=29f70e08-5bf1-4803-b94c-ed82fa37f082"
|
||||
|
||||
iscsi_absent_mounts: []
|
||||
|
||||
@ -1,30 +1,8 @@
|
||||
iscsi_portal: 10.0.0.203
|
||||
iscsi_targets: []
|
||||
iscsi_absent_mounts:
|
||||
- path: /var/lib/rancher
|
||||
|
||||
iscsi_targets:
|
||||
# PROLE-K3S-1
|
||||
- iqn: "iqn.2000-01.com.synology:synology.Target-16.292d45194a1"
|
||||
chap_user: "prole"
|
||||
chap_password: "{{ vault_iscsi_prole_password }}"
|
||||
mounts:
|
||||
- path: /var/lib/rancher
|
||||
fstype: ext4
|
||||
opts: "_netdev,noatime"
|
||||
src: "UUID=73a886bb-3f01-4e8a-9c6d-ab431c671824"
|
||||
|
||||
k3s_enabled: true
|
||||
k3s_role: agent
|
||||
k3s_node_ip: "10.0.0.207"
|
||||
k3s_flannel_iface: "wlan0"
|
||||
k3s_server_url: "https://myrddin.prole.org:6443"
|
||||
k3s_token: "{{ vault_k3s_token | default('') }}"
|
||||
k3s_node_labels:
|
||||
- "prole.org/role=spare-monitoring"
|
||||
k3s_node_taints:
|
||||
- "prole.org/exclude-cnpg=true:NoSchedule"
|
||||
k3s_write_kubeconfig_mode: "0640"
|
||||
k3s_kubeconfig_group: kubeadm
|
||||
k3s_kubeconfig_users:
|
||||
- pi
|
||||
k3s_enabled: false
|
||||
|
||||
cgroups_cmdline_candidates:
|
||||
- /boot/cmdline.txt
|
||||
|
||||
@ -3,6 +3,7 @@ pi.prole.org
|
||||
raspberry.prole.org
|
||||
myrddin.prole.org
|
||||
retropie.prole.org
|
||||
merlin.prole.org
|
||||
|
||||
[pihole]
|
||||
pi.prole.org
|
||||
@ -11,12 +12,17 @@ raspberry.prole.org
|
||||
[ad_dc]
|
||||
myrddin.prole.org
|
||||
|
||||
[k3s_hosts]
|
||||
[k3s_servers]
|
||||
myrddin.prole.org
|
||||
pi.prole.org
|
||||
retropie.prole.org
|
||||
merlin.prole.org
|
||||
|
||||
[k3s_agents]
|
||||
|
||||
[k3s_hosts:children]
|
||||
k3s_servers
|
||||
k3s_agents
|
||||
|
||||
[linux_hosts]
|
||||
pi.prole.org
|
||||
raspberry.prole.org
|
||||
|
||||
11
infrastructure/playbooks/iscsi_login.yml
Normal file
11
infrastructure/playbooks/iscsi_login.yml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: iSCSI auth/login
|
||||
hosts: iscsi
|
||||
gather_facts: false
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: iSCSI auth/login
|
||||
ansible.builtin.import_role:
|
||||
name: iscsi
|
||||
tasks_from: login
|
||||
11
infrastructure/playbooks/iscsi_mount.yml
Normal file
11
infrastructure/playbooks/iscsi_mount.yml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: iSCSI mounts
|
||||
hosts: iscsi
|
||||
gather_facts: false
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: iSCSI mounts
|
||||
ansible.builtin.import_role:
|
||||
name: iscsi
|
||||
tasks_from: mount
|
||||
13
infrastructure/playbooks/k3s_cleanup.yml
Normal file
13
infrastructure/playbooks/k3s_cleanup.yml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Cleanup / uninstall k3s (operator action)
|
||||
# Intentionally broad targeting; use --limit to select the host(s) to clean.
|
||||
hosts: all
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
- name: Uninstall k3s and remove local state
|
||||
ansible.builtin.import_role:
|
||||
name: k3s
|
||||
tasks_from: cleanup
|
||||
12
infrastructure/playbooks/merlin_mariadb_full_export.yml
Normal file
12
infrastructure/playbooks/merlin_mariadb_full_export.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Export k3s datastore database (mysqldump)
|
||||
hosts: mariadb_primary
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
- name: Export k3s datastore database
|
||||
ansible.builtin.import_role:
|
||||
name: mariadb_tools
|
||||
tasks_from: k3s_datastore_export
|
||||
12
infrastructure/playbooks/merlin_mariadb_full_import.yml
Normal file
12
infrastructure/playbooks/merlin_mariadb_full_import.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Import k3s datastore database dump into Merlin MariaDB
|
||||
hosts: mariadb_primary
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
- name: Import k3s datastore database
|
||||
ansible.builtin.import_role:
|
||||
name: mariadb_tools
|
||||
tasks_from: k3s_datastore_import
|
||||
@ -69,6 +69,8 @@
|
||||
hosts: ad_dc
|
||||
gather_facts: false
|
||||
become: true
|
||||
tags:
|
||||
- samba
|
||||
pre_tasks:
|
||||
- name: Gather minimal facts
|
||||
ansible.builtin.setup:
|
||||
|
||||
12
infrastructure/playbooks/start_k3s_agents.yml
Normal file
12
infrastructure/playbooks/start_k3s_agents.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Start k3s agents
|
||||
hosts: k3s_agents
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
- name: Start k3s-agent service
|
||||
ansible.builtin.import_role:
|
||||
name: k3s
|
||||
tasks_from: start_k3s_agents
|
||||
12
infrastructure/playbooks/start_k3s_servers.yml
Normal file
12
infrastructure/playbooks/start_k3s_servers.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Start k3s servers
|
||||
hosts: k3s_servers
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
- name: Start k3s service
|
||||
ansible.builtin.import_role:
|
||||
name: k3s
|
||||
tasks_from: start_k3s_servers
|
||||
12
infrastructure/playbooks/stop_k3s_agents.yml
Normal file
12
infrastructure/playbooks/stop_k3s_agents.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Stop k3s agents
|
||||
hosts: k3s_agents
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
- name: Stop k3s-agent service
|
||||
ansible.builtin.import_role:
|
||||
name: k3s
|
||||
tasks_from: stop_k3s_agents
|
||||
12
infrastructure/playbooks/stop_k3s_servers.yml
Normal file
12
infrastructure/playbooks/stop_k3s_servers.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Stop k3s servers
|
||||
hosts: k3s_servers
|
||||
gather_facts: false
|
||||
become: true
|
||||
serial: 1
|
||||
|
||||
tasks:
|
||||
- name: Stop k3s service
|
||||
ansible.builtin.import_role:
|
||||
name: k3s
|
||||
tasks_from: stop_k3s_servers
|
||||
@ -5,6 +5,88 @@
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Ensure marker directory exists (mkfs_once)
|
||||
ansible.builtin.file:
|
||||
path: /var/lib/prole/iscsi
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: m.mkfs_once | default(false) | bool
|
||||
|
||||
- name: Compute mkfs marker path (mkfs_once)
|
||||
ansible.builtin.set_fact:
|
||||
_iscsi_mkfs_marker: >-
|
||||
/var/lib/prole/iscsi/mkfs-
|
||||
{{ (m.src | regex_replace('^UUID=', 'uuid-')
|
||||
| regex_replace('^LABEL=', 'label-')
|
||||
| regex_replace('[^A-Za-z0-9_.-]', '_')) }}
|
||||
.done
|
||||
when: m.mkfs_once | default(false) | bool
|
||||
|
||||
- name: Check mkfs marker presence (mkfs_once)
|
||||
ansible.builtin.stat:
|
||||
path: "{{ _iscsi_mkfs_marker }}"
|
||||
register: _iscsi_mkfs_marker_stat
|
||||
when: m.mkfs_once | default(false) | bool
|
||||
|
||||
- name: Fail if mountpoint is already mounted but mkfs_once requested
|
||||
ansible.builtin.command: "findmnt -n {{ m.path }}"
|
||||
register: _iscsi_findmnt_before_mkfs
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
|
||||
- name: Refuse to mkfs when mountpoint is mounted
|
||||
ansible.builtin.fail:
|
||||
msg: "Refusing to mkfs {{ m.src }} because {{ m.path }} is already mounted ({{ _iscsi_findmnt_before_mkfs.stdout | default('') }})."
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
- _iscsi_findmnt_before_mkfs.rc == 0
|
||||
|
||||
- name: Resolve block device for mkfs (mkfs_once)
|
||||
ansible.builtin.command: >-
|
||||
{{ (m.src | regex_search('^UUID=') )
|
||||
| ternary('blkid -U ' ~ (m.src | regex_replace('^UUID=', '')),
|
||||
(m.src | regex_search('^LABEL=') )
|
||||
| ternary('blkid -L ' ~ (m.src | regex_replace('^LABEL=', '')),
|
||||
'echo ' ~ (m.src))) }}
|
||||
register: _iscsi_mkfs_dev
|
||||
changed_when: false
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
|
||||
- name: Require resolved device to look like /dev/* (mkfs_once)
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (_iscsi_mkfs_dev.stdout | default('') | trim) is match('^/dev/')
|
||||
fail_msg: "Unable to resolve a /dev/* block device for mkfs from src={{ m.src }} (got: {{ _iscsi_mkfs_dev.stdout | default('') | trim }})."
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
|
||||
- name: Re-initialize filesystem (mkfs.ext4) (mkfs_once)
|
||||
ansible.builtin.command: "mkfs.ext4 -F {{ _iscsi_mkfs_dev.stdout | trim }}"
|
||||
changed_when: true
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
|
||||
- name: Write mkfs marker (mkfs_once)
|
||||
ansible.builtin.file:
|
||||
path: "{{ _iscsi_mkfs_marker }}"
|
||||
state: touch
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when:
|
||||
- m.mkfs_once | default(false) | bool
|
||||
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
|
||||
|
||||
- name: Mount {{ m.path }}
|
||||
ansible.builtin.mount:
|
||||
path: "{{ m.path }}"
|
||||
|
||||
@ -17,3 +17,4 @@
|
||||
loop: "{{ iscsi_targets | default([]) }}"
|
||||
loop_control:
|
||||
loop_var: t
|
||||
label: "{{ t.iqn | default('<unknown-iqn>') }}"
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/fstab
|
||||
state: absent
|
||||
regexp: "^\\\\S+\\\\s+{{ (item.path | default(item)) | regex_escape }}\\\\s+"
|
||||
regexp: "^\\s*\\S+\\s+{{ (item.path | default(item)) | regex_escape }}\\s+"
|
||||
loop: "{{ iscsi_absent_mounts | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | default(item) }}"
|
||||
|
||||
@ -170,39 +170,6 @@
|
||||
- not ansible_check_mode
|
||||
- (['cgroup_memory=1', 'cgroup_enable=memory'] | reject('in', k3s_proc_cmdline.stdout | default('')) | list) | length > 0
|
||||
|
||||
- name: Preflight - detect rancher backing device
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
mp="{{ k3s_rancher_mountpoint | default('/var/lib/rancher') }}"
|
||||
|
||||
# If the mountpoint is a separate mount, use its source; otherwise fall back to the root filesystem source.
|
||||
src="$(awk -v mp="$mp" '$5==mp {for(i=1;i<=NF;i++) if($i=="-") {print $(i+2); exit}}' /proc/self/mountinfo)"
|
||||
if [[ -n "$src" ]]; then
|
||||
echo "mount:${src}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
root_src="$(awk '$5=="/" {for(i=1;i<=NF;i++) if($i=="-") {print $(i+2); exit}}' /proc/self/mountinfo)"
|
||||
echo "root:${root_src}"
|
||||
register: rancher_source
|
||||
changed_when: false
|
||||
check_mode: no
|
||||
|
||||
- name: Fail if rancher must be a separate mount but is on rootfs
|
||||
ansible.builtin.fail:
|
||||
msg: "{{ k3s_rancher_mountpoint | default('/var/lib/rancher') }} is not a separate mount ({{ rancher_source.stdout | default('') }}). Refusing to proceed (k3s_require_rancher_mount=true)."
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- k3s_require_rancher_mount | default(false) | bool
|
||||
- (rancher_source.stdout | default('')) is search('^root:')
|
||||
|
||||
- name: Fail if rancher storage is on SD
|
||||
ansible.builtin.fail:
|
||||
msg: "{{ k3s_rancher_mountpoint | default('/var/lib/rancher') }} is on SD/rootfs ({{ rancher_source.stdout | default('') }}). Refusing to proceed."
|
||||
when:
|
||||
- not ansible_check_mode
|
||||
- (rancher_source.stdout | default('')) is search("mmcblk0") or (rancher_source.stdout | default('')) is search("/dev/mmc")
|
||||
|
||||
- name: Preflight - ensure required mountpoints are mounted
|
||||
ansible.builtin.shell: |
|
||||
awk -v mp="{{ item }}" '$5==mp {found=1} END {exit(found?0:1)}' /proc/self/mountinfo
|
||||
@ -457,7 +424,7 @@
|
||||
- /etc/rancher
|
||||
- /etc/rancher/k3s
|
||||
- /etc/rancher/k3s/config.yaml.d
|
||||
- "{{ k3s_rancher_mountpoint | default('/var/lib/rancher') }}"
|
||||
- "{{ k3s_effective_data_dir | dirname }}"
|
||||
when: k3s_state != "absent"
|
||||
|
||||
- name: Detect active interface for k3s_node_ip (Retropie/Pi only)
|
||||
|
||||
21
infrastructure/roles/k3s/tasks/start_k3s_agents.yml
Normal file
21
infrastructure/roles/k3s/tasks/start_k3s_agents.yml
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Gather service facts
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Require k3s-agent service to exist
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ('k3s-agent' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s-agent' + '.service'].status != 'not-found'
|
||||
fail_msg: "k3s-agent service is not installed on {{ inventory_hostname }}."
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Start k3s-agent
|
||||
ansible.builtin.systemd:
|
||||
name: k3s-agent
|
||||
state: started
|
||||
enabled: true
|
||||
no_block: true
|
||||
when:
|
||||
- ('k3s-agent' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s-agent' + '.service'].status != 'not-found'
|
||||
21
infrastructure/roles/k3s/tasks/start_k3s_servers.yml
Normal file
21
infrastructure/roles/k3s/tasks/start_k3s_servers.yml
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Gather service facts
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Require k3s service to exist
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ('k3s' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s' + '.service'].status != 'not-found'
|
||||
fail_msg: "k3s service is not installed on {{ inventory_hostname }}."
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Start k3s
|
||||
ansible.builtin.systemd:
|
||||
name: k3s
|
||||
state: started
|
||||
enabled: true
|
||||
no_block: true
|
||||
when:
|
||||
- ('k3s' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s' + '.service'].status != 'not-found'
|
||||
21
infrastructure/roles/k3s/tasks/stop_k3s_agents.yml
Normal file
21
infrastructure/roles/k3s/tasks/stop_k3s_agents.yml
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Gather service facts
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
# Agents can sometimes be in a Restart=always loop; disabling stops the loop.
|
||||
- name: Stop and disable k3s-agent service when present
|
||||
ansible.builtin.systemd:
|
||||
name: k3s-agent
|
||||
state: stopped
|
||||
enabled: false
|
||||
when:
|
||||
- ('k3s-agent' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s-agent' + '.service'].status != 'not-found'
|
||||
|
||||
- name: Clear failed state for k3s-agent (if any)
|
||||
ansible.builtin.command: systemctl reset-failed k3s-agent
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when:
|
||||
- ('k3s-agent' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s-agent' + '.service'].status != 'not-found'
|
||||
19
infrastructure/roles/k3s/tasks/stop_k3s_servers.yml
Normal file
19
infrastructure/roles/k3s/tasks/stop_k3s_servers.yml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
- name: Gather service facts
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Stop k3s service when present
|
||||
ansible.builtin.systemd:
|
||||
name: k3s
|
||||
state: stopped
|
||||
when:
|
||||
- ('k3s' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s' + '.service'].status != 'not-found'
|
||||
|
||||
- name: Clear failed state for k3s (if any)
|
||||
ansible.builtin.command: systemctl reset-failed k3s
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when:
|
||||
- ('k3s' + '.service') in ansible_facts.services
|
||||
- ansible_facts.services['k3s' + '.service'].status != 'not-found'
|
||||
18
infrastructure/roles/k3s/tests/no_rancher_sd_guardrail.yml
Normal file
18
infrastructure/roles/k3s/tests/no_rancher_sd_guardrail.yml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Ensure k3s install preflight does not enforce rancher-on-SD guardrail
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
- name: Read k3s install tasks
|
||||
ansible.builtin.set_fact:
|
||||
_k3s_install_tasks: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/../tasks/install.yml') }}"
|
||||
|
||||
- name: Assert rancher-on-SD guardrail is absent
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (_k3s_install_tasks is search('Fail if rancher storage is on SD')) == false
|
||||
- (_k3s_install_tasks is search('Preflight - detect rancher backing device')) == false
|
||||
fail_msg: >-
|
||||
k3s install preflight should not hard-fail based on rancher storage device.
|
||||
3
infrastructure/roles/mariadb_tools/defaults/main.yml
Normal file
3
infrastructure/roles/mariadb_tools/defaults/main.yml
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
# Predictable dump directory for MariaDB exports created by this role.
|
||||
mariadb_dump_dir: /var/backups/mariadb/k3s-datastore
|
||||
@ -0,0 +1,72 @@
|
||||
---
|
||||
- name: Require k3s datastore MariaDB connection variables
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (k3s_datastore_mariadb_host | default('')) | length > 0
|
||||
- (k3s_datastore_mariadb_port | default('') | string) | length > 0
|
||||
- (k3s_datastore_mariadb_db | default('')) | length > 0
|
||||
- (k3s_datastore_mariadb_user | default('')) | length > 0
|
||||
- (k3s_datastore_mariadb_password | default('')) | length > 0
|
||||
fail_msg: >-
|
||||
Missing one or more required vars: k3s_datastore_mariadb_host/port/db/user/password.
|
||||
Ensure the k3s datastore credentials are sourced from Ansible Vault.
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Ensure dump directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ mariadb_dump_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0700"
|
||||
|
||||
- name: Verify mysqldump is available
|
||||
ansible.builtin.command: mysqldump --version
|
||||
changed_when: false
|
||||
|
||||
- name: Get timestamp for dump filename
|
||||
ansible.builtin.command: date +%Y%m%d-%H%M%S
|
||||
register: _mariadb_dump_ts
|
||||
changed_when: false
|
||||
|
||||
- name: Set dumpfile path
|
||||
ansible.builtin.set_fact:
|
||||
mariadb_dumpfile: >-
|
||||
{{ mariadb_dump_dir }}/k3s-datastore-{{ k3s_datastore_mariadb_db }}-{{ _mariadb_dump_ts.stdout }}.sql
|
||||
|
||||
- name: Export k3s datastore database with mysqldump
|
||||
ansible.builtin.command: >-
|
||||
mysqldump
|
||||
--protocol=tcp
|
||||
--host={{ k3s_datastore_mariadb_host }}
|
||||
--port={{ k3s_datastore_mariadb_port }}
|
||||
--user={{ k3s_datastore_mariadb_user }}
|
||||
--default-character-set=utf8mb4
|
||||
--single-transaction
|
||||
--quick
|
||||
--routines
|
||||
--events
|
||||
--triggers
|
||||
--skip-lock-tables
|
||||
--set-gtid-purged=OFF
|
||||
--result-file={{ mariadb_dumpfile }}
|
||||
{{ k3s_datastore_mariadb_db }}
|
||||
environment:
|
||||
MYSQL_PWD: "{{ k3s_datastore_mariadb_password }}"
|
||||
changed_when: true
|
||||
no_log: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Tighten dumpfile permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ mariadb_dumpfile }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
when:
|
||||
- mariadb_dumpfile is defined
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Print resulting dumpfile path
|
||||
ansible.builtin.debug:
|
||||
msg: "Dump created: {{ mariadb_dumpfile }}"
|
||||
@ -0,0 +1,65 @@
|
||||
---
|
||||
- name: Require mariadb_import_dumpfile
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- mariadb_import_dumpfile is defined
|
||||
- (mariadb_import_dumpfile | string) | length > 0
|
||||
fail_msg: "Set -e mariadb_import_dumpfile=/path/to/dump.sql (path must exist on the target host)."
|
||||
|
||||
- name: Require k3s datastore database name
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (k3s_datastore_mariadb_db | default('')) | length > 0
|
||||
fail_msg: "k3s_datastore_mariadb_db is empty."
|
||||
|
||||
- name: Check dumpfile exists on target host
|
||||
ansible.builtin.stat:
|
||||
path: "{{ mariadb_import_dumpfile }}"
|
||||
register: _mariadb_import_dump_stat
|
||||
|
||||
- name: Fail when dumpfile is missing
|
||||
ansible.builtin.fail:
|
||||
msg: "Dumpfile does not exist on {{ inventory_hostname }}: {{ mariadb_import_dumpfile }}"
|
||||
when: not _mariadb_import_dump_stat.stat.exists
|
||||
|
||||
- name: Ensure MariaDB service is running
|
||||
ansible.builtin.service:
|
||||
name: mariadb
|
||||
state: started
|
||||
|
||||
- name: Ensure target k3s datastore database exists
|
||||
ansible.builtin.command: >-
|
||||
mysql --protocol=socket --user=root
|
||||
--execute="CREATE DATABASE IF NOT EXISTS `{{ k3s_datastore_mariadb_db }}`"
|
||||
changed_when: false
|
||||
|
||||
- name: Refuse dumpfiles that appear to reference other databases
|
||||
ansible.builtin.command: >-
|
||||
{{ ansible_playbook_python }} -c
|
||||
"import re,sys
|
||||
dump=sys.argv[1]
|
||||
target_db=sys.argv[2]
|
||||
bad=set()
|
||||
with open(dump,'r',encoding='utf-8',errors='ignore') as f:
|
||||
for ln in f:
|
||||
if ln.startswith('CREATE DATABASE'):
|
||||
bad.add('CREATE DATABASE')
|
||||
if ln.startswith('USE '):
|
||||
m=re.match(r'^USE\\s+`?([^` ;]+)`?;?', ln.strip())
|
||||
if m and m.group(1) != target_db:
|
||||
bad.add(m.group(1))
|
||||
if bad:
|
||||
print('Dumpfile references non-target DB(s) or contains CREATE DATABASE:', ','.join(sorted(bad)))
|
||||
sys.exit(2)
|
||||
print('Dumpfile scope OK')"
|
||||
{{ mariadb_import_dumpfile }}
|
||||
{{ k3s_datastore_mariadb_db }}
|
||||
changed_when: false
|
||||
|
||||
- name: Import dumpfile into k3s datastore database (deliberate operator action)
|
||||
ansible.builtin.shell: >-
|
||||
set -euo pipefail;
|
||||
mysql --protocol=socket --user=root {{ k3s_datastore_mariadb_db }} < {{ mariadb_import_dumpfile }}
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
30
infrastructure/roles/mariadb_tools/tests/sanity.yml
Normal file
30
infrastructure/roles/mariadb_tools/tests/sanity.yml
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: Sanity check mariadb_tools task files
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Read export task file
|
||||
ansible.builtin.set_fact:
|
||||
_export_tasks: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/../tasks/k3s_datastore_export.yml') }}"
|
||||
|
||||
- name: Assert export task file contains expected primitives
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _export_tasks is search('mysqldump')
|
||||
- _export_tasks is search('--single-transaction')
|
||||
- _export_tasks is search('mariadb_dumpfile')
|
||||
fail_msg: "Export task file missing expected mysqldump/flags/output handling."
|
||||
|
||||
- name: Read import task file
|
||||
ansible.builtin.set_fact:
|
||||
_import_tasks: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/../tasks/k3s_datastore_import.yml') }}"
|
||||
|
||||
- name: Assert import task requires mariadb_import_dumpfile
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _import_tasks is search('mariadb_import_dumpfile')
|
||||
- _import_tasks is search('mysql --protocol=socket')
|
||||
fail_msg: "Import task file missing required var checks and/or socket import path."
|
||||
@ -71,7 +71,16 @@
|
||||
changed_when: false
|
||||
when: _netplan_cfg is changed
|
||||
|
||||
- name: Apply netplan config
|
||||
ansible.builtin.command: netplan apply
|
||||
changed_when: false
|
||||
- name: Apply netplan config (async; may disrupt SSH)
|
||||
block:
|
||||
- name: Apply netplan
|
||||
ansible.builtin.command: netplan apply
|
||||
async: 60
|
||||
poll: 0
|
||||
changed_when: false
|
||||
|
||||
- name: Wait for host to come back after netplan apply
|
||||
ansible.builtin.wait_for_connection:
|
||||
delay: 3
|
||||
timeout: 180
|
||||
when: _netplan_cfg is changed
|
||||
|
||||
@ -18,7 +18,7 @@ samba_ad_dc_realm: >-
|
||||
samba_ad_dc_workgroup: >-
|
||||
{{ (samba_ad_dc_child_id | length > 0)
|
||||
| ternary((samba_ad_dc_child_id | upper), "PROLE") }}
|
||||
samba_ad_dc_netbios_name: "{{ samba_ad_dc_workgroup }}"
|
||||
samba_ad_dc_netbios_name: "{{ inventory_hostname_short | upper }}"
|
||||
samba_ad_dc_server_string: "{{ samba_ad_dc_realm }} AD DC"
|
||||
|
||||
# Role/services
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
[global]
|
||||
dns forwarder = {{ samba_ad_dc_dns_forwarders | join(' ') }}
|
||||
|
||||
netbios name = {{ samba_ad_dc_netbios_name }}
|
||||
netbios name = {{ (samba_ad_dc_netbios_name | default(inventory_hostname_short)) | upper }}
|
||||
realm = {{ samba_ad_dc_realm }}
|
||||
server string = {{ samba_ad_dc_server_string }}
|
||||
server role = {{ samba_ad_dc_server_role }}
|
||||
|
||||
45
infrastructure/roles/samba_ad_dc/tests/netbios_name.yml
Normal file
45
infrastructure/roles/samba_ad_dc/tests/netbios_name.yml
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
- name: Add a representative AD DC host for template rendering
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
become: false
|
||||
|
||||
tasks:
|
||||
- name: Add myrddin.prole.org as a local-connection host
|
||||
ansible.builtin.add_host:
|
||||
name: myrddin.prole.org
|
||||
groups: samba_ad_dc_test_hosts
|
||||
ansible_connection: local
|
||||
|
||||
- name: Render smb.conf and assert netbios name derives from short hostname
|
||||
hosts: samba_ad_dc_test_hosts
|
||||
connection: local
|
||||
gather_facts: false
|
||||
become: false
|
||||
|
||||
vars:
|
||||
_rendered_path: /tmp/samba_ad_dc_smb.conf
|
||||
|
||||
tasks:
|
||||
- name: Load samba_ad_dc role defaults
|
||||
ansible.builtin.include_vars:
|
||||
file: "{{ playbook_dir }}/../defaults/main.yml"
|
||||
|
||||
- name: Render smb.conf template
|
||||
ansible.builtin.template:
|
||||
src: "{{ playbook_dir }}/../templates/smb.conf.j2"
|
||||
dest: "{{ _rendered_path }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: Read rendered smb.conf
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ _rendered_path }}"
|
||||
register: _smb_conf
|
||||
|
||||
- name: Assert netbios name is the uppercase short hostname
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (_smb_conf.content | b64decode) is search('(?m)^\\s*netbios name\\s*=\\s*MYRDDIN\\s*$')
|
||||
fail_msg: >-
|
||||
Expected smb.conf to contain: netbios name = MYRDDIN
|
||||
@ -1 +1 @@
|
||||
103
|
||||
104
|
||||
48
tools/export_mariadb_synology.sh
Executable file
48
tools/export_mariadb_synology.sh
Executable file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SRC_HOST="synology.prole.org"
|
||||
SRC_PORT="3306"
|
||||
SRC_DB="k3s"
|
||||
SRC_USER="root"
|
||||
DB_PASSWORD='7EAs3!kF2!7eQypP'
|
||||
|
||||
OUT_DIR="${1:-$PWD}"
|
||||
TS="$(date +%Y%m%d-%H%M%S)"
|
||||
OUT_FILE="${OUT_DIR%/}/k3s-datastore-${SRC_HOST}-${TS}.sql"
|
||||
|
||||
DUMP_BIN="${DUMP_BIN:-mysqldump}" # or mariadb-dump if that’s what you have
|
||||
|
||||
extra=()
|
||||
# MySQL 8 client compat (harmless if absent)
|
||||
if "${DUMP_BIN}" --help 2>/dev/null | grep -q -- '--column-statistics'; then
|
||||
extra+=(--column-statistics=0)
|
||||
fi
|
||||
# Some clients support it; some MariaDB builds don’t. Only add when supported.
|
||||
if "${DUMP_BIN}" --help 2>/dev/null | grep -q -- '--set-gtid-purged'; then
|
||||
extra+=(--set-gtid-purged=OFF)
|
||||
fi
|
||||
|
||||
# IMPORTANT for your Ansible import role’s safety check:
|
||||
# - do NOT use --databases / --all-databases (those emit CREATE DATABASE)
|
||||
# - dump exactly one DB (k3s), so any USE statement matches the target DB
|
||||
MYSQL_PWD="${DB_PASSWORD}" "${DUMP_BIN}" \
|
||||
--protocol=tcp \
|
||||
--host="${SRC_HOST}" \
|
||||
--port="${SRC_PORT}" \
|
||||
--user="${SRC_USER}" \
|
||||
--default-character-set=utf8mb4 \
|
||||
--single-transaction \
|
||||
--quick \
|
||||
--routines \
|
||||
--events \
|
||||
--triggers \
|
||||
--skip-lock-tables \
|
||||
--skip-add-locks \
|
||||
--hex-blob \
|
||||
"${extra[@]}" \
|
||||
"${SRC_DB}" \
|
||||
>"${OUT_FILE}"
|
||||
|
||||
chmod 0600 "${OUT_FILE}"
|
||||
echo "Wrote: ${OUT_FILE}"
|
||||
Loading…
Reference in New Issue
Block a user