chore: excise prole.org ansible infrastructure for knoe-db fork

knoe-db deploys cluster-native (mode k3d for local dev, k8s for the
shared company cluster, k3s still supported for individual homelab
collaborators via conf/k3s.cfg). The ansible-managed distributed cluster
of prole.org machines is no longer the target topology, and the Ansible
inventory in this tree targets only the original author's specific hosts
(merlin/gandalf/myrddin/pi/raspberry/retropie.prole.org). It is neither
portable nor useful for the new deployment shape.

Removed:
- infrastructure/inventory/  (hosts.ini + 7 *.prole.org host_vars)
- infrastructure/playbooks/  (11 playbooks, all hardcoded to *.prole.org)
- infrastructure/roles/      (20 roles: pihole, samba AD DC, mariadb,
                              netplan, iscsi, prole_ssl, etc. -- all
                              tied to the bare-metal prole.org topology)
- infrastructure/setup/      (cr_ansible_user.sh, cr_samba_family_users.sh,
                              filesystem.txt -- prole.org bootstrap)
- infrastructure/deployments/svc-check-helm/
                              (legacy; etc/init_kong.sh already tears
                               down svc-check resources on every run)
- infrastructure/ansible.cfg (references deleted inventory)
- ansible.cfg, ansible.sh, ansible_min.cfg, ansible_recovery.log  (root;
                              orphaned without inventory)
- vault_backup/              (ansible-vault encrypted prole.org secrets
                              backup: ad_dc_vault.yml, all_prole_vault.yml,
                              iscsi_vault.yml)
- tools/                     (dashboard.sh, ghcr_docker_login.sh,
                              k3s-cluster-rename.sh, manage-node.sh,
                              export_mariadb_synology.sh,
                              prepare_mariadb_usb.sh, and a dumped
                              synology.prole.org mariadb .sql file --
                              all prole.org-era deployment helpers)

Kept: scripts/ (cluster ops), bin/ (prole-env.sh, prole-kpf.sh dev
shims), conf/k3s.cfg / conf/k3d.cfg (still supported modes; any
*.prole.org defaults in those files are legitimate mode fallbacks for
collaborators who keep running their own k3s/k3d setups). Test
fixtures referencing *.prole.org hostnames are left as-is (regression
data).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chrisfu 2026-04-20 18:53:03 -07:00
parent e44ff8e476
commit 5a769f273b
186 changed files with 0 additions and 10397 deletions

View File

@ -1,26 +0,0 @@
[defaults]
inventory = infrastructure/inventory/hosts.ini
roles_path = infrastructure/roles
collections_paths = infrastructure/collections
interpreter_python = auto_silent
deprecation_warnings = False
callback_plugins = lib/ansible/plugins/callback
callbacks_enabled = run_logger
stdout_callback = default
result_format = yaml
forks = 20
timeout = 30
host_key_checking = True
[privilege_escalation]
become = True
become_method = sudo
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

View File

@ -1,143 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export ANSIBLE_CONFIG="${ROOT_DIR}/ansible.cfg"
# Defaults
MODE="fg" # fg|bg
PLAYBOOK="infrastructure/playbooks/site.yml"
VAULT_PASS_FILE=""
if [[ -f "${ROOT_DIR}/.vault_pass" ]]; then
VAULT_PASS_FILE="${ROOT_DIR}/.vault_pass"
fi
LIMIT="" # optional
TAGS="" # optional
EXTRA_ARGS=() # passthrough
# Use local logs if PROLE_LOGS is not writable or looks like a remote path
LOG_BASE="${ROOT_DIR}/.ansible/logs"
if [[ -n "${PROLE_LOGS:-}" ]]; then
# If it's a relative path, or it exists and is writable, or its parent is writable
if [[ ! "${PROLE_LOGS}" =~ ^/ ]] || [[ -w "${PROLE_LOGS}" ]] || [[ -w "$(dirname "${PROLE_LOGS}" 2>/dev/null)" ]]; then
LOG_BASE="${PROLE_LOGS}"
fi
fi
LOG_BASE="${LOG_BASE%/}"
LOG_DIR="${LOG_BASE}/ansible"
SYSLOG_HOST="" # e.g. loghost.prole.org
SYSLOG_PORT="514"
SYSLOG_TAG="ansible"
usage() {
cat <<EOF
Usage: ./ansible.sh [options] [-- <extra ansible-playbook args>]
Options:
-p, --playbook PATH Playbook path (default: ${PLAYBOOK})
-l, --limit HOSTS Limit hosts
-t, --tags TAGS Tags
-v, --vault-pass-file FILE Vault password file
-m, --mode fg|bg Run in foreground or background (default: fg)
--syslog-host HOST Send start/end markers via UDP syslog to HOST
--syslog-port PORT Syslog UDP port (default: 514)
--syslog-tag TAG Syslog tag (default: ansible)
-h, --help Show help
Examples:
./ansible.sh -l pi.prole.org -t iscsi -v .vault_pass
./ansible.sh -m bg -p infrastructure/playbooks/site.yml -v .vault_pass
./ansible.sh --syslog-host loghost.prole.org -m bg -v .vault_pass -- -vv
EOF
}
send_syslog() {
local msg="$1"
if [[ -n "${SYSLOG_HOST}" ]]; then
# -d = UDP, -n host, -P port
logger -d -n "${SYSLOG_HOST}" -P "${SYSLOG_PORT}" -t "${SYSLOG_TAG}" -- "${msg}" || true
fi
}
while [[ $# -gt 0 ]]; do
case "$1" in
-p|--playbook) PLAYBOOK="$2"; shift 2 ;;
-l|--limit) LIMIT="$2"; shift 2 ;;
-t|--tags) TAGS="$2"; shift 2 ;;
-v|--vault-pass-file) VAULT_PASS_FILE="$2"; shift 2 ;;
-m|--mode) MODE="$2"; shift 2 ;;
--syslog-host) SYSLOG_HOST="$2"; shift 2 ;;
--syslog-port) SYSLOG_PORT="$2"; shift 2 ;;
--syslog-tag) SYSLOG_TAG="$2"; shift 2 ;;
--) shift; EXTRA_ARGS+=("$@"); break ;;
-h|--help) usage; exit 0 ;;
*) EXTRA_ARGS+=("$1"); shift ;;
esac
done
# Default to k3s hosts for the main site run (avoid touching non-k3s Linux/Pi hosts unless explicitly requested)
if [[ -z "${LIMIT}" ]]; then
if [[ "${PLAYBOOK}" =~ (^|/)infrastructure/playbooks/site\.yml$ ]]; then
LIMIT="k3s_hosts"
fi
fi
mkdir -p "${LOG_DIR}"
ts="$(date +%Y%m%d-%H%M%S)"
logfile="${LOG_DIR}/ansible-${ts}.log"
cmd=(ansible-playbook "${PLAYBOOK}")
[[ -n "${LIMIT}" ]] && cmd+=("--limit" "${LIMIT}")
[[ -n "${TAGS}" ]] && cmd+=("--tags" "${TAGS}")
[[ -n "${VAULT_PASS_FILE}" ]] && cmd+=("--vault-password-file" "${VAULT_PASS_FILE}")
cmd+=("${EXTRA_ARGS[@]}")
send_syslog "START playbook=${PLAYBOOK} limit=${LIMIT:-<none>} tags=${TAGS:-<none>} log=${logfile}"
echo "ANSIBLE_CONFIG=${ANSIBLE_CONFIG}"
echo "LOGFILE=${logfile}"
echo "CMD: ${cmd[*]}"
if [[ "${MODE}" == "fg" ]]; then
# Stream to terminal and file
color_env=()
use_pty=false
if [[ -z "${NO_COLOR:-}" ]] && [[ -z "${ANSIBLE_NOCOLOR:-}" ]]; then
[[ -z "${ANSIBLE_FORCE_COLOR:-}" ]] && color_env+=("ANSIBLE_FORCE_COLOR=true")
[[ -z "${PY_COLORS:-}" ]] && color_env+=("PY_COLORS=1")
# Ensure a useful terminal type for ANSI colors when invoked from wrappers.
if [[ -z "${TERM:-}" ]] || [[ "${TERM}" == "dumb" ]]; then
color_env+=("TERM=xterm-256color")
fi
if command -v script >/dev/null 2>&1; then
use_pty=true
fi
fi
run_cmd=("${cmd[@]}")
if [[ "${use_pty}" == "true" ]]; then
# `tee` breaks TTY detection; wrap in a pseudo-tty so Ansible keeps colors.
run_cmd=(script -qF /dev/null "${cmd[@]}")
fi
set +e
env "${color_env[@]}" "${run_cmd[@]}" 2>&1 | tee "${logfile}"
rc=${PIPESTATUS[0]}
set -e
else
# Background: nohup to logfile
nohup "${cmd[@]}" >"${logfile}" 2>&1 &
rc=0
echo "Started in background (pid $!)"
fi
if [[ "${MODE}" == "fg" ]]; then
if [[ $rc -eq 0 ]]; then
send_syslog "END OK playbook=${PLAYBOOK} limit=${LIMIT:-<none>} tags=${TAGS:-<none>} log=${logfile}"
else
send_syslog "END FAIL rc=${rc} playbook=${PLAYBOOK} limit=${LIMIT:-<none>} tags=${TAGS:-<none>} log=${logfile}"
fi
exit $rc
fi

View File

@ -1,5 +0,0 @@
[defaults]
stdout_callback = default
interpreter_python = auto_silent
host_key_checking = False
forks = 1

View File

@ -1,48 +0,0 @@
ANSIBLE_CONFIG=/Users/chrisfu/dev/prole/ansible.cfg
LOGFILE=/Users/chrisfu/dev/prole/.ansible/logs/ansible/ansible-20260216-053756.log
CMD: ansible-playbook infrastructure/playbooks/site.yml --limit k3s_hosts --vault-password-file .vault_pass
PLAY [iSCSI hosts and filesystem mounts] ***************************************
TASK [Gathering Facts] *********************************************************
ok: [retropie.prole.org]
ok: [myrddin.prole.org]
ok: [pi.prole.org]
TASK [iscsi : Install iSCSI dependencies] **************************************
ok: [myrddin.prole.org]
ok: [retropie.prole.org]
ok: [pi.prole.org]
TASK [iscsi : Ensure iscsid enabled] *******************************************
ok: [myrddin.prole.org]
ok: [retropie.prole.org]
ok: [pi.prole.org]
TASK [iscsi : Configure and login iSCSI targets] *******************************
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for pi.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-14.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/var/lib/rancher', 'fstype': 'ext4', 'opts': '_netdev,noatime', 'src': 'UUID=466eefa2-9a7c-4bac-a136-d1d3ec35dae1'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for pi.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-19.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/var/log/pihole', 'fstype': 'ext4', 'opts': '_netdev,noatime', 'src': 'UUID=56b21ec3-2826-4171-b909-a6715223f9a4'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for retropie.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-16.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/var/lib/rancher', 'fstype': 'ext4', 'opts': '_netdev,noatime', 'src': 'UUID=73a886bb-3f01-4e8a-9c6d-ab431c671824'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for myrddin.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-11.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/prole/d001', 'fstype': 'xfs', 'opts': '_netdev,noatime', 'src': 'UUID=c07dc0f2-cf60-4da5-b68e-ea45e86d473e'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for myrddin.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-12.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/prole/d002', 'fstype': 'xfs', 'opts': '_netdev,noatime', 'src': 'UUID=fe087fb0-a321-4767-b838-b4385e81693e'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for myrddin.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-13.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/prole/d003', 'fstype': 'xfs', 'opts': '_netdev,noatime', 'src': 'UUID=757f1ee4-dc23-414b-b595-e3058c0744f0'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for myrddin.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-17.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/prole/home', 'fstype': 'ext4', 'opts': '_netdev,noatime', 'src': 'UUID=607718c8-467a-474f-8e36-80ceafe1beb4'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for myrddin.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-1.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/prole/logs', 'fstype': 'ext4', 'opts': '_netdev,noatime', 'src': 'UUID=5335affd-b1ab-4134-a1c0-be88ab965309'}]})
included: /Users/chrisfu/dev/prole/infrastructure/roles/iscsi/tasks/iscsi_target.yml for myrddin.prole.org => (item={'iqn': 'iqn.2000-01.com.synology:synology.Target-15.292d45194a1', 'chap_user': 'prole', 'chap_password': 'zVAx33rQK6RkU6Bv', 'mounts': [{'path': '/var/lib/rancher', 'fstype': 'ext4', 'opts': '_netdev,noatime', 'src': 'UUID=c8320979-d3eb-4b40-93e0-ee452a4b9780'}]})
TASK [iscsi : Ensure node record exists for target] ****************************
ok: [pi.prole.org]
TASK [iscsi : Create node record if missing] ***********************************
skipping: [pi.prole.org]
TASK [iscsi : Configure CHAP authmethod] ***************************************
changed: [pi.prole.org]
TASK [iscsi : Configure CHAP username] *****************************************
changed: [pi.prole.org]
TASK [iscsi : Configure CHAP password] *****************************************
changed: [pi.prole.org]
TASK [iscsi : Check if target session is already logged in] ********************

View File

@ -1,25 +0,0 @@
[defaults]
# Note: When running from the project root, the root ansible.cfg is used.
# This file is for running ansible-playbook from within the infrastructure/ directory.
inventory = inventory/hosts.ini
roles_path = roles
collections_paths = collections
interpreter_python = auto_silent
callback_plugins = ../lib/ansible/plugins/callback
callbacks_enabled = run_logger
# For Ansible < 2.13, we use community.general.yaml
# For Ansible >= 2.13, we should use result_format=yaml in ansible.builtin.default
# To maintain compatibility, we use the default callback and set result_format
stdout_callback = default
result_format = yaml
forks = 20
timeout = 30
host_key_checking = True
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False
[ssh_connection]
pipelining = True

View File

@ -1,6 +0,0 @@
apiVersion: v2
name: svc-check
description: Static svc.prole.org check site served via Kong
type: application
version: 0.1.0
appVersion: "1.0.0"

View File

@ -1,7 +0,0 @@
{{- define "svc-check.name" -}}
svc-check
{{- end -}}
{{- define "svc-check.fullname" -}}
{{- printf "%s" (include "svc-check.name" .) -}}
{{- end -}}

View File

@ -1,12 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: svc-check-content
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
app: svc-check-web
data:
index.html: |-
{{ .Values.svcCheck.html | nindent 4 }}
binaryData:
prole-type.gif: {{ .Values.svcCheck.gifB64 | default "" | quote }}

View File

@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: svc-check-web
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
app: svc-check-web
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: svc-check-web
template:
metadata:
labels:
app: svc-check-web
spec:
containers:
- name: nginx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
volumeMounts:
- name: content
mountPath: /usr/share/nginx/html
volumes:
- name: content
configMap:
name: svc-check-content
items:
- key: index.html
path: index.html
- key: prole-type.gif
path: prole-type.gif

View File

@ -1,26 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: svc-prole-ingress
namespace: {{ .Values.kong.namespace }}
annotations:
kubernetes.io/ingress.class: {{ .Values.ingress.className }}
cert-manager.io/cluster-issuer: {{ .Values.ingress.clusterIssuer }}
traefik.ingress.kubernetes.io/router.priority: "10"
spec:
ingressClassName: {{ .Values.ingress.className }}
tls:
- hosts:
- {{ .Values.svcCheck.domain }}
secretName: {{ .Values.ingress.tlsSecretName }}
rules:
- host: {{ .Values.svcCheck.domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Values.kong.proxyServiceName }}
port:
number: 8000

View File

@ -1,25 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.kong.configMapName }}
namespace: {{ .Values.kong.namespace }}
data:
kong.yml: |
_format_version: "3.0"
_transform: true
services:
- name: kubeconfig
url: http://prole-svc.knoe-db.svc.cluster.local:8080
routes:
- name: kubeconfig
paths:
- /k3s/kube_config.sh
- name: svc-check
url: http://svc-check-web.{{ .Values.namespace | default .Release.Namespace }}.svc.cluster.local:80
routes:
- name: svc-check-root
hosts:
- {{ .Values.svcCheck.domain }}
paths:
- /
strip_path: false

View File

@ -1,4 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .Values.namespace | default .Release.Namespace }}

View File

@ -1,14 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: svc-check-web
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
app: svc-check-web
spec:
selector:
app: svc-check-web
ports:
- name: http
port: 80
targetPort: http

View File

@ -1,47 +0,0 @@
namespace: svc-check
replicaCount: 1
image:
repository: nginx
tag: "1.27-alpine"
pullPolicy: IfNotPresent
svcCheck:
domain: svc.prole.org
title: svc.prole.org
subtitle: served by k3s -> Kong on myrddin.prole.org
gifB64: ""
html: |-
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>svc.prole.org</title>
<style>
:root { font-family: "Helvetica Neue", Arial, sans-serif; background:#0f1419; color:#f5f7fa; }
body { margin:0; display:flex; min-height:100vh; align-items:center; justify-content:center; }
.wrap { text-align:center; padding:32px 40px; background:#1b2330; border:1px solid #2c3645; border-radius:18px; box-shadow:0 18px 38px rgba(0,0,0,0.35); }
img { max-width:320px; width:70vw; border-radius:12px; box-shadow:0 6px 18px rgba(0,0,0,0.35); }
h1 { margin:18px 0 8px; letter-spacing:0.5px; font-size:22px; }
p { margin:0; color:#9fb3c8; font-size:14px; }
</style>
</head>
<body>
<div class="wrap">
<img src="/prole-type.gif" alt="Prole type" />
<h1>svc.prole.org</h1>
<p>served by k3s -> Kong on myrddin.prole.org</p>
</div>
</body>
</html>
kong:
namespace: default
configMapName: prole-svc-kong-config
proxyServiceName: prole-svc-kong
ingress:
className: traefik
clusterIssuer: letsencrypt-prod
tlsSecretName: svc-prole-org-tls

View File

@ -1,8 +0,0 @@
---
samba_dns_server: "myrddin.prole.org"
samba_dns_admin_user: "administrator"
# We'll wire the password with vault next
samba_dns_admin_pass: "{{ vault_samba_dns_admin_pass }}"
rsyslog_role: receiver

View File

@ -1,8 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
39636637353364306639373863656430333863373635663936373464303431643761393034373061
3365323233353233393437333535636136386162353231320a303431336432316464346538303366
34323662643737623963313431613930646131653130663762633130626162386435656239386435
3566323235666531330a306366616438653230383438626630626236363839376138376336626634
34646532363036303761383939303838373136663562386661366363316666623837616661303831
65363062313533383936323864316336633130313237366661663133663234643462356136656432
326565363832386462393864393434656337

View File

@ -1,202 +0,0 @@
---
prole_domain: prole.org
# Source of truth imported from name.com export (A/CNAME/MX/TXT)
# Notes:
# - "name: '@'" means the zone apex (prole.org)
# - no IPv6 records included (per request)
prole_dns_records:
- name: aventage
type: A
value: 10.0.0.206
ttl: 300
- name: book.svc
type: A
value: 185.158.133.1
ttl: 300
- name: fairyland
type: A
value: 10.0.0.208
ttl: 300
- name: k8s
type: CNAME
value: zinfandel.prole.org
ttl: 300
- name: loghost
type: A
value: 10.0.0.3
ttl: 300
- name: mc
type: A
value: 73.15.20.166
ttl: 300
- name: merlin
type: A
value: 10.0.0.6
ttl: 300
- name: morana
type: A
value: 10.0.0.66
ttl: 300
- name: morgoth
type: A
value: 10.0.0.204
ttl: 300
- name: myrddin
type: A
value: 10.0.0.3
ttl: 300
- name: ollama
type: A
value: 73.15.20.166
ttl: 300
- name: pi
type: A
value: 10.0.0.5
ttl: 300
- name: "@"
type: MX
value: alt1.aspmx.l.google.com
ttl: 3600
priority: 5
- name: "@"
type: MX
value: alt2.aspmx.l.google.com
ttl: 3600
priority: 5
- name: "@"
type: MX
value: alt3.aspmx.l.google.com
ttl: 3600
priority: 10
- name: "@"
type: MX
value: alt4.aspmx.l.google.com
ttl: 3600
priority: 10
- name: "@"
type: MX
value: aspmx.l.google.com
ttl: 3600
priority: 1
- name: raspberry
type: A
value: 10.0.0.4
ttl: 300
- name: retropie
type: A
value: 10.0.0.207
ttl: 300
- name: svc
type: A
value: 73.15.20.166
ttl: 300
- name: synology
type: A
value: 10.0.0.203
ttl: 300
- name: www
type: CNAME
value: ghs.googlehosted.com
ttl: 300
- name: zinfandel
type: A
value: 10.0.0.205
ttl: 300
- name: _lovable.book.svc
type: TXT
value: lovable_verify=a6369596ca0ae0b00fde155591c566c05a9844302a70727ba69a74b56c58927f
ttl: 300
# Traefik Load Balancer addresses for k3s front-door
prole_traefik_lb_ips:
- 10.0.0.3
- 10.0.0.6
# k3s Front Door names (normalized to prole.org)
prole_k3s_front_door_names:
- git
- svc
- api
- db
- supabase
- registry
# Convenience list: internal RFC1918 A records only (useful for Samba AD DNS population)
prole_internal_a_records:
- fqdn: aventage.prole.org
ipv4s: [10.0.0.206]
- fqdn: fairyland.prole.org
ipv4s: [10.0.0.208]
- fqdn: loghost.prole.org
ipv4s: [10.0.0.3]
- fqdn: merlin.prole.org
ipv4s: [10.0.0.6]
- fqdn: morana.prole.org
ipv4s: [10.0.0.66]
- fqdn: morgoth.prole.org
ipv4s: [10.0.0.204]
- fqdn: myrddin.prole.org
ipv4s: [10.0.0.3]
- fqdn: pi.prole.org
ipv4s: [10.0.0.5]
- fqdn: raspberry.prole.org
ipv4s: [10.0.0.4]
- fqdn: retropie.prole.org
ipv4s: [10.0.0.207]
- fqdn: synology.prole.org
ipv4s: [10.0.0.203]
- fqdn: zinfandel.prole.org
ipv4s: [10.0.0.205]
# k3s front-door records (public and internal.prole.org)
prole_k3s_dns_records:
- fqdn: git.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: git.internal.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: svc.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: svc.internal.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: api.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: api.internal.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: db.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: db.internal.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: supabase.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
- fqdn: supabase.internal.prole.org
ipv4s: [10.0.0.3, 10.0.0.6]
prole_k3s_cname_records:
- fqdn: registry.prole.org
target: svc.prole.org
- fqdn: registry.internal.prole.org
target: svc.internal.prole.org

View File

@ -1,27 +0,0 @@
k3s_state: present # present|stopped|absent
k3s_version: "" # e.g. "v1.29.7+k3s1" or empty for latest
k3s_disable:
- local-storage
k3s_write_kubeconfig_mode: "0640"
k3s_kubeconfig_group: kubeadm
k3s_kubeconfig_users:
- pi
# K3s state storage
# `/var/lib/rancher` is treated as local host storage for all k3s nodes.
# (On `myrddin.prole.org` this is backed by a locally attached USB3 SSD; shared/iSCSI
# storage is reserved for other data paths such as `/prole/*`.)
k3s_rancher_mountpoint: /var/lib/rancher
k3s_rancher_storage_class: local
k3s_data_dir: "{{ k3s_rancher_mountpoint }}/k3s"
# Post-provisioning, persisted labels applied via kubectl (delegated to control-plane)
k3s_node_labels:
myrddin.prole.org:
prole.org/storage: fast
prole.org/ingress: primary
prole.org/stateful: "true"
merlin.prole.org:
prole.org/role: stateless
prole.org/storage: slow

View File

@ -1,10 +0,0 @@
---
# Production service inventory (pre-orchestration reference list).
# Keep this list minimal and explicit; production currently only includes Pi-hole.
production_services:
- name: pihole
hosts:
- pi.prole.org
- raspberry.prole.org
dependencies:
- iscsi

View File

@ -1,7 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
64373935366135353230383931313131666463323262663363623934346165653933323636386635
6437306634623062323464646431323333306464316634620a306466353266663262396435613537
37383737653930633932336633666430653462343634363838383439666163326638616264323562
6138393633396631650a643864373833656232393164616138386638373932393363363731383033
65366163323932633033376338646231656264363431636334656363336462616665346634613966
3637646539373535353234653461613036353435626561396466

View File

@ -1,35 +0,0 @@
---
ansible_user: ansible
ansible_ssh_private_key_file: "~/.ssh/id_ed25519_ansible"
ansible_become: true
ansible_become_method: sudo
# DNS topology vars shared across roles
prole_domain: "prole.org"
ad_dc_ip: "10.0.0.3"
lan_reverse_zone: "0.0.10.in-addr.arpa"
# k3s default Pod/Service CIDR reverse zones
k3s_reverse_zones:
- "42.10.in-addr.arpa"
- "43.10.in-addr.arpa"
# Seed PTRs (adjust octets to match your actual IPs)
ptr_records:
- last_octet: "3"
fqdn: "myrddin.prole.org"
- last_octet: "4"
fqdn: "raspberry.prole.org"
- last_octet: "5"
fqdn: "pi.prole.org"
- last_octet: "207"
fqdn: "retropie.prole.org"
rsyslog_server: myrddin.prole.org
# Prole management
prole_home: "/opt/prole"
prole_repo_url: "https://github.com/prole-org/prole.git"
prole_version: "main"
prole_logs_dir: "/opt/prole/logs/chrisfu"

View File

@ -1,8 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
33623434353765353030333339626631656163343239353230643430356466306461663835383566
3564333737623034343837616632386462306462366538390a623538373034383839376261653734
36653238393437656332373965663866653730343864333063366462303661356366323262363839
3961633266353131310a313930346164393031623037393339356539616639343536353163343036
61353535633234363535633437356162626234356139323531643534613961633166393135356562
30306634646130353665656262393132656632373634353765316630643665356331363435366165
356261383336383736336336356564386337

View File

@ -1 +0,0 @@
vault_k3s_token: "K107c8c6000488eca4a067d8a73119bbae2f07b4ea1bac7d8d3dc9c500cbb8acb18::server:04572345810eae2f9619a6ed4239702b"

View File

@ -1,7 +0,0 @@
---
# iSCSI (override per-host)
iscsi_portal: 10.0.0.203
iscsi_chap_user: "prole"
iscsi_chap_password: ""
iscsi_target_iqn: ""
iscsi_device_hint: "" # optional: /dev/disk/by-path/... or leave empty

View File

@ -1,7 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
38323535303264613662616137643166323864306366663631346332383333313638303863383363
6536636562663531313431376664313562323966383964330a636361666235623337346636343936
35313563393764623162386666333133653536643230313431633766346436343430353364333537
3033396638346431610a356533346663336332326633396134656466396331336566663165633439
38326531313532383361613535396666333165313536633035323934353666656332356530363430
6237616366346364626537326132613666613232373864643230

View File

@ -1,10 +0,0 @@
---
# MariaDB provisioning (non-secret defaults).
# Remote TCP admin user created by `mariadb_primary` for management.
k3s_mariadb_admin_user: prole_admin
# Keep the k3s datastore password vaulted.
# For consistency with existing k3s server configuration, reuse the AD DC's vaulted Samba DNS admin password.
# (This ensures k3s can authenticate to the datastore after migration.)
k3s_datastore_mariadb_password: "{{ hostvars[groups['ad_dc'][0]].vault_samba_dns_admin_pass }}"

View File

@ -1,9 +0,0 @@
$ANSIBLE_VAULT;1.1;AES256
62353131343066373436383738303262393762653331356334376236343636643531643666373965
3832393033653232313165636239326163336138643134310a343639346433353933363836616561
37303838303230663632313430613231323162376436333766316530323436666161343935363537
3638653238316261650a383931353462356636366663316532636230656639363165376136326138
38616265346438333465306539643433313464633633363965303366326362323632643032633361
62386261333634626131333832383339636537663261393662613861663032336330383737626161
62313533396231623561653133303832663765646635386230343662336231353432326437363132
33363361396433623533

View File

@ -1,13 +0,0 @@
---
# Your internal domain used by Samba AD (split-horizon is fine)
prole_domain: "prole.org"
# Samba AD DC IP
ad_dc_ip: "10.0.0.3"
# Reverse zone for 10.0.0.0/24
lan_reverse_zone: "0.0.10.in-addr.arpa"
# Performance/resilience knobs
pihole_dns_forward_max: 300
pihole_sqlite_busy_timeout: 5000

View File

@ -1,38 +0,0 @@
---
hostname: gandalf
fqdn: gandalf.prole.org
ansible_host: 10.0.0.7
ansible_user: ansible
k3s_enabled: true
k3s_role: agent
k3s_cluster_init: false
k3s_server_url: "https://myrddin.prole.org:6443"
k3s_service_node_labels:
- "prole.org/node-role=general"
k3s_node_taints: []
k3s_rancher_mount_required: true
k3s_rancher_mount_src: /external/rancher
k3s_rancher_mount_fstype: none
k3s_rancher_mount_opts: bind
k3s_rancher_mount_passno: 0
k3s_required_mounts:
- /var/lib/rancher
- /synology/d005
iscsi_portal: 10.0.0.203:3260
iscsi_targets:
# PROLE-DATA-5
- iqn: "iqn.2000-01.com.synology:synology.Target-14.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- name: d005
path: /synology/d005
device: /dev/disk/by-path/ip-10.0.0.203:3260-iscsi-iqn.2000-01.com.synology:synology.Target-14.292d45194a1-lun-1-part1
fstype: ext4
opts: "_netdev,noatime,nofail"

View File

@ -1,3 +0,0 @@
---
ansible_become: false
ansible_connection: local

View File

@ -1,85 +0,0 @@
---
ansible_host: 10.0.0.6
netplan_static_enabled: true
netplan_static_iface: eth0
netplan_static_address: 10.0.0.6/24
netplan_static_gateway4: 10.0.0.1
netplan_static_nameservers:
- 10.0.0.1
# MariaDB primary (USB-backed)
# Permanent portable USB disk for MariaDB (merlin)
# - Disk: UUID=3669ddf4-5884-43ae-84b2-150ec117a2b1 (ext4, ~1.82T)
# - Mount: /external (by UUID; device name varies after USB re-enumeration)
# - MariaDB datadir is expected at /srv/mariadb/mariadb, but is bind-mounted to /external/mariadb
# - Replaced borrowed WD My Book (exFAT) after disk-full kine deadlock incident (2026-04-03)
mariadb_external_enabled: true
mariadb_external_device: /dev/disk/by-uuid/3669ddf4-5884-43ae-84b2-150ec117a2b1
mariadb_external_mountpoint: /external
mariadb_external_fstype: ext4
# ext4 supports POSIX ownership; chown -R mysql:mysql was applied during migration from sda1.
mariadb_external_mount_opts: "defaults,nofail,x-systemd.device-timeout=10"
mariadb_external_src_datadir: /srv/mariadb/mariadb
mariadb_external_dst_datadir: /external/mariadb
mariadb_external_backup_datadir: /srv/mariadb/mariadb.pre-external
mariadb_external_migration_marker: /external/mariadb/.prole-mariadb-external-migrated
# k3s worker (stateless workloads)
k3s_enabled: true
k3s_role: agent
k3s_cluster_init: false
k3s_server_url: "https://myrddin.prole.org:6443"
# k3s datastore now lives on merlin.
# NOTE: Use IP to avoid DNS drift during migration/cutover.
k3s_datastore_mariadb_host: 10.0.0.6
k3s_datastore_mariadb_port: 3306
k3s_datastore_mariadb_db: k3s
k3s_datastore_mariadb_user: prole_k3s
k3s_datastore_endpoint: "mysql://{{ k3s_datastore_mariadb_user }}:{{ k3s_datastore_mariadb_password | urlencode }}@tcp({{ k3s_datastore_mariadb_host }}:{{ k3s_datastore_mariadb_port }})/{{ k3s_datastore_mariadb_db }}"
k3s_tls_sans:
- merlin.prole.org
k3s_service_node_labels:
- "prole.org/node-role=general"
k3s_node_taints: []
k3s_required_mounts:
- /synology/d002
- /synology/d004
# iSCSI
# `/var/lib/rancher` is local host storage (do not manage it via iSCSI).
iscsi_portal: 10.0.0.203:3260
iscsi_targets:
# PROLE-DATA-2
- iqn: "iqn.2000-01.com.synology:synology.Target-12.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- name: d002
path: /synology/d002
fstype: xfs
opts: "_netdev,noatime"
src: "UUID=fe087fb0-a321-4767-b838-b4385e81693e"
# PROLE-DATA-4
# Repurposed from myrddin's former `/var/lib/rancher` Synology LUN.
# Repartitioned 2026-04-03: raw XFS on /dev/sdb → GPT + /dev/sdb1 (XFS).
- iqn: "iqn.2000-01.com.synology:synology.Target-15.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- name: d004
path: /synology/d004
fstype: xfs
opts: "_netdev,noatime"
src: "UUID=45143352-142d-4e47-8508-9eb84c4c1f29"
iscsi_absent_mounts: []

View File

@ -1,117 +0,0 @@
iscsi_portal: 10.0.0.203
iscsi_targets:
# PROLE-DATA-1
- iqn: "iqn.2000-01.com.synology:synology.Target-11.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- path: /synology/d001
fstype: xfs
opts: "_netdev,noatime"
src: "UUID=c07dc0f2-cf60-4da5-b68e-ea45e86d473e"
# PROLE-HOME
- iqn: "iqn.2000-01.com.synology:synology.Target-17.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- path: /prole/home
fstype: ext4
opts: "_netdev,noatime"
src: "UUID=607718c8-467a-474f-8e36-80ceafe1beb4"
# PROLE-LOGS
- iqn: "iqn.2000-01.com.synology:synology.Target-1.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- path: /prole/logs
fstype: ext4
opts: "_netdev,noatime"
src: "UUID=5335affd-b1ab-4134-a1c0-be88ab965309"
iscsi_absent_mounts:
- /opt/prole/logs/chrisfu
# Migration cleanup: PROLE-DATA-2/3 are moving off myrddin
- /prole/d002
- /prole/d003
- /synology/d002
- /synology/d003
# Migration cleanup: ensure myrddin fully logs out of LUNs that are moving to other hosts
iscsi_absent_targets:
# PROLE-DATA-2
- "iqn.2000-01.com.synology:synology.Target-12.292d45194a1"
# PROLE-DATA-3
- "iqn.2000-01.com.synology:synology.Target-13.292d45194a1"
ad_dc_enabled: true
k3s_enabled: true
k3s_cluster_init: false
k3s_role: server
# K3s state storage (local)
# `/var/lib/rancher` is now backed by a locally attached USB3 SSD on myrddin.
# Shared/iSCSI storage remains reserved for `/prole/*` data paths.
k3s_rancher_mountpoint: /var/lib/rancher
k3s_rancher_storage_class: local_usb3_ssd
# Guardrail: `/var/lib/rancher` must be a dedicated mount (external SSD) and k3s
# must never start if it resolves to the same backing device as `/`.
k3s_rancher_mount_required: true
k3s_rancher_mount_src: "UUID=2e457f8f-fc85-48e7-a7fc-29aad0059268"
k3s_rancher_mount_fstype: ext4
k3s_rancher_mount_opts: "noatime"
k3s_rancher_mount_passno: 2
k3s_token: "{{ vault_k3s_token | default('') }}" # store this in vault
k3s_datastore_mariadb_host: 10.0.0.6
k3s_datastore_mariadb_port: 3306
k3s_datastore_mariadb_db: k3s
k3s_datastore_mariadb_user: prole_k3s
# Password is the same as the Samba administrator password and must remain in Ansible Vault.
k3s_datastore_mariadb_password: "{{ vault_samba_dns_admin_pass }}"
k3s_datastore_endpoint: "mysql://{{ k3s_datastore_mariadb_user }}:{{ k3s_datastore_mariadb_password | urlencode }}@tcp({{ k3s_datastore_mariadb_host }}:{{ k3s_datastore_mariadb_port }})/{{ k3s_datastore_mariadb_db }}"
k3s_tls_sans:
- myrddin.prole.org
k3s_service_node_labels:
- "prole.org/node-role=db"
- "prole.org/role=control"
# Keep general workloads off the control-plane node; allow only explicitly-tolerating pods.
k3s_node_taints:
- "node-role.kubernetes.io/control-plane:NoSchedule"
# More forgiving IO/etcd timeouts to reduce error-correction churn on slow storage
k3s_kube_apiserver_args:
- "request-timeout=60s"
- "min-request-timeout=60"
k3s_kube_controller_manager_args:
- "leader-elect-lease-duration=600s"
- "leader-elect-renew-deadline=420s"
- "leader-elect-retry-period=60s"
- "node-monitor-grace-period=10m"
- "node-startup-grace-period=10m"
k3s_kube_scheduler_args:
- "leader-elect-lease-duration=600s"
- "leader-elect-renew-deadline=420s"
- "leader-elect-retry-period=60s"
k3s_cloud_controller_manager_args:
- "leader-elect-lease-duration=900s"
- "leader-elect-renew-deadline=600s"
- "leader-elect-retry-period=90s"
k3s_kubelet_args:
- "housekeeping-interval=30s"
k3s_write_kubeconfig_mode: "0640"
k3s_kubeconfig_group: kubeadm
k3s_kubeconfig_users:
- pi
k3s_required_mounts:
- /synology/d001

View File

@ -1,51 +0,0 @@
iscsi_portal: 10.0.0.203
k3s_required_mounts:
- /synology/d003
iscsi_targets:
# `/var/lib/rancher` is local host storage (do not manage it via iSCSI).
# PROLE-DATA-3
- iqn: "iqn.2000-01.com.synology:synology.Target-13.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- path: /synology/d003
fstype: xfs
opts: "_netdev,noatime"
src: "UUID=757f1ee4-dc23-414b-b595-e3058c0744f0"
# PROLE-PI-2
- iqn: "iqn.2000-01.com.synology:synology.Target-19.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- path: /var/log/pihole
fstype: ext4
opts: "_netdev,noatime"
src: "UUID=56b21ec3-2826-4171-b909-a6715223f9a4"
# k3s intentionally disabled — pi.prole.org is a dedicated pihole node;
# insufficient RAM for k3s workloads; agent manually stopped 2026-04-01.
k3s_enabled: false
k3s_state: absent
k3s_rancher_mount_required: true
k3s_rancher_mount_src: /synology/d003/rancher
k3s_rancher_mount_fstype: none
k3s_rancher_mount_opts: bind
k3s_rancher_mount_passno: 0
k3s_role: agent
k3s_cluster_init: false
k3s_server_url: "https://myrddin.prole.org:6443"
k3s_service_node_labels:
- "prole.org/node-role=general"
- "prole.org/role=observer"
k3s_node_taints: []
k3s_write_kubeconfig_mode: "0640"
k3s_kubeconfig_group: kubeadm
k3s_kubeconfig_users:
- pi

View File

@ -1,12 +0,0 @@
iscsi_portal: 10.0.0.203
iscsi_targets:
# PROLE-PI-1
- iqn: "iqn.2000-01.com.synology:synology.Target-18.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- path: /var/log/pihole
fstype: ext4
opts: "_netdev,noatime"
src: "UUID=17b375e9-c615-48bb-a3cd-b60672d67996"

View File

@ -1,8 +0,0 @@
iscsi_targets: []
iscsi_absent_mounts: []
k3s_enabled: false
cgroups_cmdline_candidates:
- /boot/cmdline.txt
- /boot/firmware/cmdline.txt

View File

@ -1,50 +0,0 @@
[iscsi]
pi.prole.org
raspberry.prole.org
myrddin.prole.org
retropie.prole.org
merlin.prole.org
gandalf.prole.org
[pihole]
pi.prole.org
raspberry.prole.org
[ad_dc]
myrddin.prole.org
[k3s_servers]
myrddin.prole.org
[k3s_agents]
merlin.prole.org
gandalf.prole.org
[k3s_hosts:children]
k3s_servers
k3s_agents
[linux_hosts]
pi.prole.org
raspberry.prole.org
myrddin.prole.org
retropie.prole.org
merlin.prole.org
gandalf.prole.org
[ssl_hosts]
myrddin.prole.org
[mariadb_primary]
merlin.prole.org
[mariadb_replica]
raspberry.prole.org
[mariadb:children]
mariadb_primary
mariadb_replica
[merlin_bootstrap]
merlin ansible_host=10.0.0.36 ansible_user=ansible

View File

@ -1,9 +0,0 @@
---
- name: Debian audit mode
hosts: linux_hosts
become: true
gather_facts: true
check_mode: true
diff: true
roles:
- audit

View File

@ -1,24 +0,0 @@
---
- name: Bootstrap local ansible user + key + sudo
hosts: all
become: true
tasks:
- name: Ensure local ansible user exists
ansible.builtin.user:
name: ansible
shell: /bin/bash
create_home: true
- name: Install authorized key for local ansible user
ansible.posix.authorized_key:
user: ansible
state: present
key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_ed25519_ansible.pub') }}"
- name: Allow passwordless sudo for local ansible
ansible.builtin.copy:
dest: /etc/sudoers.d/90-ansible
content: "ansible ALL=(ALL) NOPASSWD:ALL\n"
owner: root
group: root
mode: "0440"

View File

@ -1,7 +0,0 @@
---
- name: Ensure cgroup kernel parameters
hosts: k3s_hosts
become: true
serial: 1
roles:
- cgroups

View File

@ -1,59 +0,0 @@
---
- name: Validate k3s API endpoint reachability
hosts: k3s_hosts
gather_facts: false
become: false
vars:
k3s_default_port: 6443
tasks:
- name: Select k3s init host
ansible.builtin.set_fact:
k3s_init_host: "{{ item }}"
when: hostvars[item].k3s_cluster_init | default(false) | bool
loop: "{{ groups['k3s_hosts'] }}"
run_once: true
- name: Select k3s server URL (if provided)
ansible.builtin.set_fact:
k3s_api_url: "{{ hostvars[item].k3s_server_url }}"
when:
- hostvars[item].k3s_server_url is defined
- hostvars[item].k3s_server_url | length > 0
loop: "{{ groups['k3s_hosts'] }}"
run_once: true
- name: Fallback to init host URL
ansible.builtin.set_fact:
k3s_api_url: "https://{{ k3s_init_host | default(groups['k3s_hosts'][0]) }}:{{ k3s_default_port }}"
when: k3s_api_url is not defined or k3s_api_url | length == 0
run_once: true
- name: Extract k3s API host
ansible.builtin.set_fact:
k3s_api_host: "{{ k3s_api_url | regex_replace('^https?://', '') | regex_replace(':.*$', '') }}"
run_once: true
- name: Check TCP port 6443
ansible.builtin.wait_for:
host: "{{ k3s_api_host }}"
port: "{{ k3s_default_port }}"
timeout: 10
delegate_to: localhost
become: false
run_once: true
- name: Check /readyz endpoint
ansible.builtin.uri:
url: "{{ k3s_api_url }}/readyz"
method: GET
status_code: [200, 401, 403]
validate_certs: false
timeout: 5
delegate_to: localhost
become: false
run_once: true
- name: Report k3s API endpoint
ansible.builtin.debug:
msg: "k3s API reachable at {{ k3s_api_url }}"
run_once: true

View File

@ -1,86 +0,0 @@
---
# Disable k3s-agent on pi.prole.org and remove it from the cluster.
# Safe to run against a live cluster — only touches pi.prole.org.
# Storage (iSCSI) and pihole configuration are intentionally preserved.
#
# Usage:
# ansible-playbook -i inventory/hosts.ini playbooks/disable_pi_k3s.yml
# ansible-playbook -i inventory/hosts.ini playbooks/disable_pi_k3s.yml --check
- name: Drain and delete pi node from k3s control-plane
hosts: k3s_servers
gather_facts: false
become: true
tasks:
- name: Drain pi.prole.org (evict pods, ignore DaemonSets)
ansible.builtin.command:
cmd: kubectl drain pi.prole.org
--ignore-daemonsets
--delete-emptydir-data
--force
--timeout=120s
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: _drain
changed_when: _drain.rc == 0
failed_when: false
- name: Delete pi node from cluster
ansible.builtin.command:
cmd: kubectl delete node pi.prole.org --ignore-not-found
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: _delete_node
changed_when: "'deleted' in _delete_node.stdout"
failed_when: false
- name: Stop and disable k3s-agent on pi.prole.org
hosts: pi.prole.org
gather_facts: false
become: true
tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
- name: Check for k3s-agent systemd unit
ansible.builtin.stat:
path: /etc/systemd/system/k3s-agent.service
register: _agent_unit
- name: Stop k3s-agent service
ansible.builtin.systemd:
name: k3s-agent
state: stopped
enabled: false
when:
- _agent_unit.stat.exists | default(false)
- not ansible_check_mode
- name: Check for generic k3s systemd unit (agent variant)
ansible.builtin.stat:
path: /etc/systemd/system/k3s.service
register: _k3s_unit
- name: Stop k3s service if present (some installs use k3s not k3s-agent)
ansible.builtin.systemd:
name: k3s
state: stopped
enabled: false
when:
- _k3s_unit.stat.exists | default(false)
- not ansible_check_mode
failed_when: false
- name: Confirm k3s processes are not running
ansible.builtin.command: pgrep -c k3s
register: _k3s_procs
changed_when: false
failed_when: false
- name: Report k3s process status
ansible.builtin.debug:
msg: >-
k3s processes on pi.prole.org:
{{ 'NONE (clean)' if _k3s_procs.rc != 0 else _k3s_procs.stdout + ' process(es) still running' }}

View File

@ -1,58 +0,0 @@
---
- name: Cleanup stale iSCSI nodes and restart open-iscsi
hosts: myrddin.prole.org
become: true
tasks:
- name: List configured iSCSI nodes
ansible.builtin.command: iscsiadm -m node
register: iscsiadm_nodes
changed_when: false
failed_when: false
- name: Build desired iSCSI target list
ansible.builtin.set_fact:
desired_iqns: "{{ iscsi_targets | default([]) | map(attribute='iqn') | list }}"
- name: Build current iSCSI node list
ansible.builtin.set_fact:
iscsi_node_iqns: "{{ iscsiadm_nodes.stdout_lines | default([]) | map('split') | map('first') | list | unique }}"
- name: Determine stale iSCSI nodes
ansible.builtin.set_fact:
iscsi_stale_iqns: "{{ iscsi_node_iqns | difference(desired_iqns | default([])) }}"
- name: Logout stale iSCSI nodes
ansible.builtin.command: >
iscsiadm -m node -T {{ item }} -p {{ iscsi_portal }} --logout
loop: "{{ iscsi_stale_iqns | default([]) }}"
loop_control:
label: "{{ item }}"
failed_when: false
- name: Delete stale iSCSI node records
ansible.builtin.command: >
iscsiadm -m node -o delete -T {{ item }} -p {{ iscsi_portal }}
loop: "{{ iscsi_stale_iqns | default([]) }}"
loop_control:
label: "{{ item }}"
failed_when: false
- name: Reset open-iscsi failed state
ansible.builtin.command: systemctl reset-failed open-iscsi
changed_when: false
failed_when: false
- name: Restart open-iscsi
ansible.builtin.service:
name: open-iscsi
state: restarted
- name: Check open-iscsi status
ansible.builtin.command: systemctl is-failed open-iscsi
register: open_iscsi_state
changed_when: false
failed_when: false
- name: Report open-iscsi status
ansible.builtin.debug:
msg: "open-iscsi state: {{ open_iscsi_state.stdout | default('unknown') }}"

View File

@ -1,11 +0,0 @@
---
- 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

View File

@ -1,11 +0,0 @@
---
- name: iSCSI mounts
hosts: iscsi
gather_facts: false
become: true
tasks:
- name: iSCSI mounts
ansible.builtin.import_role:
name: iscsi
tasks_from: mount

View File

@ -1,13 +0,0 @@
---
- 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

View File

@ -1,63 +0,0 @@
---
- name: Delete k3s nodes and remove cluster token from vault
hosts: k3s_hosts
become: true
tasks:
- name: Remove existing k3s installation and data
ansible.builtin.import_role:
name: k3s
tasks_from: cleanup
- name: Remove k3s token from Ansible vault
hosts: localhost
connection: local
gather_facts: false
vars:
vault_k3s_path: "{{ playbook_dir }}/../inventory/group_vars/all/vault_k3s.yml"
k3s_vault_password_file: "{{ lookup('env', 'ANSIBLE_VAULT_PASSWORD_FILE') | default('', true) }}"
tasks:
- name: Require vault password file for token deletion
ansible.builtin.assert:
that:
- k3s_vault_password_file is defined
- k3s_vault_password_file | length > 0
fail_msg: "Set k3s_vault_password_file to a vault password file path."
- name: Ensure vault file exists
ansible.builtin.stat:
path: "{{ vault_k3s_path }}"
register: vault_k3s_file
- name: Fail when vault file is missing
ansible.builtin.fail:
msg: "Vault file not found at {{ vault_k3s_path }}"
when: not vault_k3s_file.stat.exists
- name: Read vault_k3s.yml header
ansible.builtin.command: "head -n 1 {{ vault_k3s_path }}"
register: vault_k3s_header
changed_when: false
- name: Mark vault_k3s.yml encryption state
ansible.builtin.set_fact:
vault_k3s_encrypted: "{{ vault_k3s_header.stdout is search('^\\$ANSIBLE_VAULT') }}"
- name: Decrypt vault_k3s.yml
ansible.builtin.command: >-
ansible-vault decrypt {{ vault_k3s_path }}
--vault-password-file {{ k3s_vault_password_file }}
changed_when: true
when: vault_k3s_encrypted | bool
- name: Remove vault_k3s_token entry
ansible.builtin.lineinfile:
path: "{{ vault_k3s_path }}"
regexp: '^vault_k3s_token:'
state: absent
- name: Encrypt vault_k3s.yml
ansible.builtin.command: >-
ansible-vault encrypt {{ vault_k3s_path }}
--vault-password-file {{ k3s_vault_password_file }}
changed_when: true
when: vault_k3s_encrypted | bool

View File

@ -1,211 +0,0 @@
---
- name: Prepare k3s repair metadata
hosts: k3s_hosts
gather_facts: false
run_once: true
tasks:
- name: Initialize k3s repair source host
ansible.builtin.set_fact:
k3s_repair_source_host: ""
delegate_to: localhost
delegate_facts: true
- name: Select k3s init server as repair source
ansible.builtin.set_fact:
k3s_repair_source_host: "{{ item }}"
loop: "{{ groups['k3s_hosts'] }}"
when: hostvars[item].k3s_cluster_init | default(false) | bool
delegate_to: localhost
delegate_facts: true
- name: Require a k3s init server as repair source
ansible.builtin.assert:
that:
- hostvars['localhost'].k3s_repair_source_host | length > 0
fail_msg: "No k3s init server found. Ensure a host has k3s_cluster_init: true."
- name: Ensure k3s node token exists on source
ansible.builtin.stat:
path: "{{ k3s_data_dir }}/server/node-token"
register: k3s_node_token
delegate_to: "{{ hostvars['localhost'].k3s_repair_source_host }}"
- name: Fail when k3s node token is missing
ansible.builtin.fail:
msg: "k3s node token not found at {{ k3s_data_dir }}/server/node-token"
when: not k3s_node_token.stat.exists
- name: Read k3s node token from source
ansible.builtin.slurp:
src: "{{ k3s_data_dir }}/server/node-token"
register: k3s_node_token_raw
delegate_to: "{{ hostvars['localhost'].k3s_repair_source_host }}"
- name: Read k3s server CA from source
ansible.builtin.slurp:
src: "{{ k3s_data_dir }}/server/tls/server-ca.crt"
register: k3s_server_ca_raw
delegate_to: "{{ hostvars['localhost'].k3s_repair_source_host }}"
- name: Store k3s repair metadata on controller
ansible.builtin.set_fact:
k3s_repair_token: "{{ k3s_node_token_raw.content | b64decode | trim }}"
k3s_repair_server_ca: "{{ k3s_server_ca_raw.content | b64decode }}"
k3s_repair_server_ca_sha: "{{ (k3s_server_ca_raw.content | b64decode) | hash('sha256') }}"
delegate_to: localhost
delegate_facts: true
- name: Update inventory vault token to match source
hosts: localhost
gather_facts: false
vars:
k3s_repair_token: "{{ hostvars['localhost'].k3s_repair_token | default('') }}"
vault_k3s_path: "{{ playbook_dir }}/../inventory/group_vars/all/vault_k3s.yml"
tasks:
- name: Require k3s repair token
ansible.builtin.assert:
that:
- k3s_repair_token | length > 0
fail_msg: "k3s repair token is empty. Cannot update vault_k3s.yml."
- name: Update vault k3s token
ansible.builtin.lineinfile:
path: "{{ vault_k3s_path }}"
regexp: '^vault_k3s_token:'
line: "vault_k3s_token: \"{{ k3s_repair_token }}\""
- name: Diagnose and repair k3s configuration
hosts: k3s_hosts
become: true
gather_facts: false
vars:
k3s_repair_source_host: "{{ hostvars['localhost'].k3s_repair_source_host }}"
k3s_repair_token: "{{ hostvars['localhost'].k3s_repair_token | default('') }}"
k3s_repair_server_ca: "{{ hostvars['localhost'].k3s_repair_server_ca | default('') }}"
k3s_repair_server_ca_sha: "{{ hostvars['localhost'].k3s_repair_server_ca_sha | default('') }}"
k3s_config_path: /etc/rancher/k3s/config.yaml
k3s_agent_ca_path: "{{ k3s_data_dir }}/agent/server-ca.crt"
k3s_server_ca_path: "{{ k3s_data_dir }}/server/tls/server-ca.crt"
pre_tasks:
- name: Require k3s repair metadata
ansible.builtin.assert:
that:
- k3s_repair_token | length > 0
- k3s_repair_server_ca | length > 0
fail_msg: "Missing k3s repair metadata from controller."
- name: Set k3s service name
ansible.builtin.set_fact:
k3s_service_name: "{{ 'k3s' if k3s_role == 'server' else 'k3s-agent' }}"
tasks:
- name: Ensure k3s config exists
ansible.builtin.stat:
path: "{{ k3s_config_path }}"
register: k3s_config
- name: Fail when k3s config is missing
ansible.builtin.fail:
msg: "k3s config is missing at {{ k3s_config_path }}. Run the k3s role first."
when: not k3s_config.stat.exists
- name: Read k3s config
ansible.builtin.slurp:
src: "{{ k3s_config_path }}"
register: k3s_config_raw
when: k3s_config.stat.exists
- name: Read k3s token from config
ansible.builtin.shell: |
awk -F': ' '/^token:/ {gsub(/"/, "", $2); print $2}' "{{ k3s_config_path }}"
register: k3s_config_token_cmd
changed_when: false
- name: Read k3s server URL from config
ansible.builtin.shell: |
awk -F': ' '/^server:/ {gsub(/"/, "", $2); print $2}' "{{ k3s_config_path }}"
register: k3s_config_server_cmd
changed_when: false
- name: Read k3s data-dir from config
ansible.builtin.shell: |
awk -F': ' '/^data-dir:/ {gsub(/"/, "", $2); print $2}' "{{ k3s_config_path }}"
register: k3s_config_data_dir_cmd
changed_when: false
- name: Store parsed k3s config values
ansible.builtin.set_fact:
k3s_config_token: "{{ k3s_config_token_cmd.stdout | trim }}"
k3s_config_server: "{{ k3s_config_server_cmd.stdout | trim }}"
k3s_config_data_dir: "{{ k3s_config_data_dir_cmd.stdout | trim }}"
- name: Read agent server CA when present
ansible.builtin.slurp:
src: "{{ k3s_agent_ca_path }}"
register: k3s_agent_ca_raw
when: k3s_role != 'server'
failed_when: false
- name: Compute agent CA checksum
ansible.builtin.set_fact:
k3s_agent_ca_sha: >-
{{ (k3s_agent_ca_raw.content | default('') | b64decode | hash('sha256')) if (k3s_agent_ca_raw.content is defined) else '' }}
when: k3s_role != 'server'
- name: Report k3s config state
ansible.builtin.debug:
msg:
host: "{{ inventory_hostname }}"
role: "{{ k3s_role }}"
service: "{{ k3s_service_name }}"
config_token: "{{ k3s_config_token | default('') }}"
token_matches_source: "{{ (k3s_config_token | default('')) == k3s_repair_token }}"
config_server: "{{ k3s_config_server | default('') }}"
server_matches_inventory: "{{ (k3s_role == 'server') or ((k3s_config_server | default('')) == (k3s_server_url | default(''))) }}"
config_data_dir: "{{ k3s_config_data_dir | default('') }}"
data_dir_matches_inventory: "{{ (k3s_config_data_dir | default('')) == (k3s_data_dir | default('')) }}"
agent_ca_matches_source: "{{ (k3s_role == 'server') or ((k3s_agent_ca_sha | default('')) == k3s_repair_server_ca_sha) }}"
- name: Sync k3s token into config
ansible.builtin.lineinfile:
path: "{{ k3s_config_path }}"
regexp: '^token:'
line: "token: \"{{ k3s_repair_token }}\""
mode: "0640"
notify: Restart k3s service
- name: Sync k3s server URL into config for non-init nodes
ansible.builtin.lineinfile:
path: "{{ k3s_config_path }}"
regexp: '^server:'
line: "server: \"{{ k3s_server_url }}\""
mode: "0640"
when:
- not (k3s_cluster_init | default(false) | bool)
- k3s_server_url | default('') | length > 0
notify: Restart k3s service
- name: Ensure agent cert directory exists
ansible.builtin.file:
path: "{{ k3s_data_dir }}/agent"
state: directory
mode: "0755"
when: k3s_role != 'server'
- name: Sync server CA to agent
ansible.builtin.copy:
content: "{{ k3s_repair_server_ca }}"
dest: "{{ k3s_agent_ca_path }}"
owner: root
group: root
mode: "0644"
when: k3s_role != 'server'
notify: Restart k3s service
handlers:
- name: Restart k3s service
ansible.builtin.service:
name: "{{ k3s_service_name }}"
state: restarted
- import_playbook: k3s_fetch_kubeconfig.yml

View File

@ -1,26 +0,0 @@
---
- name: Fetch k3s kubeconfig from primary node
hosts: myrddin.prole.org
gather_facts: false
vars:
k3s_kubeconfig_src: /etc/rancher/k3s/k3s.yaml
k3s_api_server: "https://{{ inventory_hostname }}:6443"
local_kubeconfig_path: "{{ playbook_dir }}/../../prole-k3s.kubeconfig"
tasks:
- name: Read k3s kubeconfig from primary node
ansible.builtin.slurp:
src: "{{ k3s_kubeconfig_src }}"
register: k3s_kubeconfig_raw
become: true
- name: Rewrite kubeconfig server to primary endpoint
ansible.builtin.set_fact:
k3s_kubeconfig_rendered: "{{ k3s_kubeconfig_raw.content | b64decode | regex_replace('server: https://[^\\s]+', 'server: ' ~ k3s_api_server) }}"
- name: Save kubeconfig locally for etc/init scripts
ansible.builtin.copy:
content: "{{ k3s_kubeconfig_rendered }}"
dest: "{{ local_kubeconfig_path }}"
mode: "0600"
delegate_to: localhost
run_once: true

View File

@ -1,30 +0,0 @@
---
# Import local Docker image tarballs directly into k3s containerd on all nodes.
#
# Usage:
# ansible-playbook k3s_import_images.yml \
# -e '{"k3s_import_image_tars": ["/path/to/image1.tar", "/path/to/image2.tar"]}'
#
# This avoids the slow docker-push-to-registry flow by copying tarballs
# directly to each k3s node and importing via `k3s ctr images import`.
- name: Import container images into k3s nodes
hosts: k3s_hosts
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
- name: Skip import on hosts where k3s is disabled
ansible.builtin.meta: end_host
when: not (k3s_enabled | default(true) | bool)
tasks:
- name: Import image tarballs
ansible.builtin.import_role:
name: k3s
tasks_from: import_images
when: (k3s_import_images | default([])) | length > 0

View File

@ -1,24 +0,0 @@
---
- name: Install and start k3s on a single host
hosts: k3s_hosts
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Require a single host target
ansible.builtin.assert:
that:
- ansible_play_hosts_all | length == 1
fail_msg: "This playbook targets one host. Use --limit <host>."
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
tasks:
- name: Install and start k3s
ansible.builtin.import_role:
name: k3s
tasks_from: install

View File

@ -1,102 +0,0 @@
---
- name: Prepare MariaDB datastore for k3s (create DB/user/grants)
hosts: k3s_hosts
gather_facts: false
become: false
serial: 1
vars:
k3s_mariadb_host: "{{ k3s_datastore_mariadb_host | default('synology.prole.org') }}"
k3s_mariadb_port: "{{ k3s_datastore_mariadb_port | default(3306) }}"
k3s_mariadb_db: "{{ k3s_datastore_mariadb_db | default('k3s') }}"
k3s_mariadb_user: "{{ k3s_datastore_mariadb_user | default('prole_k3s') }}"
k3s_mariadb_user_password: "{{ k3s_datastore_mariadb_password | default('') }}"
# MariaDB admin credentials (must be stored in Ansible Vault by the operator)
k3s_mariadb_admin_user: "{{ k3s_mariadb_admin_user | default('root') }}"
k3s_mariadb_admin_password: "{{ k3s_mariadb_admin_password | default('') }}"
# Where to allow the k3s user to connect from (tighten this if possible)
k3s_mariadb_user_host: "{{ k3s_mariadb_user_host | default('%') }}"
pre_tasks:
- name: Only run on k3s server nodes
ansible.builtin.meta: end_host
when: k3s_role | default('') != 'server'
- name: Require k3s MariaDB user password (vaulted)
ansible.builtin.assert:
that:
- k3s_mariadb_user_password | length > 0
fail_msg: >-
k3s_mariadb_user_password is empty. Ensure k3s_datastore_mariadb_password is set
(and sourced from Ansible Vault, e.g. vault_samba_dns_admin_pass).
- name: Require MariaDB admin password for non-check runs
ansible.builtin.assert:
that:
- k3s_mariadb_admin_password | length > 0
fail_msg: >-
k3s_mariadb_admin_password is empty.
Provide a vaulted MariaDB admin password (e.g. in an encrypted group_vars/*/vault.yml).
when: not ansible_check_mode
- name: Verify mysql client is available on this host
ansible.builtin.command: mysql --version
changed_when: false
tasks:
- name: Create k3s database (if missing)
ansible.builtin.command: >-
mysql
--protocol=tcp
--host={{ k3s_mariadb_host }}
--port={{ k3s_mariadb_port }}
--user={{ k3s_mariadb_admin_user }}
--execute="CREATE DATABASE IF NOT EXISTS `{{ k3s_mariadb_db }}`"
environment:
MYSQL_PWD: "{{ k3s_mariadb_admin_password }}"
changed_when: false
when: not ansible_check_mode
no_log: true
- name: Create/ensure k3s MariaDB user exists
ansible.builtin.command: >-
mysql
--protocol=tcp
--host={{ k3s_mariadb_host }}
--port={{ k3s_mariadb_port }}
--user={{ k3s_mariadb_admin_user }}
--execute="CREATE USER IF NOT EXISTS '{{ k3s_mariadb_user }}'@'{{ k3s_mariadb_user_host }}' IDENTIFIED BY '{{ k3s_mariadb_user_password }}'"
environment:
MYSQL_PWD: "{{ k3s_mariadb_admin_password }}"
changed_when: false
when: not ansible_check_mode
no_log: true
- name: Grant admin privileges on k3s database to k3s MariaDB user
ansible.builtin.command: >-
mysql
--protocol=tcp
--host={{ k3s_mariadb_host }}
--port={{ k3s_mariadb_port }}
--user={{ k3s_mariadb_admin_user }}
--execute="GRANT ALL PRIVILEGES ON `{{ k3s_mariadb_db }}`.* TO '{{ k3s_mariadb_user }}'@'{{ k3s_mariadb_user_host }}'"
environment:
MYSQL_PWD: "{{ k3s_mariadb_admin_password }}"
changed_when: false
when: not ansible_check_mode
no_log: true
- name: Flush privileges
ansible.builtin.command: >-
mysql
--protocol=tcp
--host={{ k3s_mariadb_host }}
--port={{ k3s_mariadb_port }}
--user={{ k3s_mariadb_admin_user }}
--execute="FLUSH PRIVILEGES"
environment:
MYSQL_PWD: "{{ k3s_mariadb_admin_password }}"
changed_when: false
when: not ansible_check_mode
no_log: true

View File

@ -1,54 +0,0 @@
---
- name: Roll out k3s config for external MariaDB datastore (servers)
hosts: k3s_hosts
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Require explicit acknowledgement (this changes the k3s datastore)
ansible.builtin.assert:
that:
- k3s_datastore_migration_ack | default(false) | bool
fail_msg: >-
Refusing to proceed without k3s_datastore_migration_ack=true.
Switching k3s datastore backends may require a rebuild/migration plan.
- name: Only run on k3s server nodes
ansible.builtin.meta: end_host
when: k3s_role | default('') != 'server'
- name: Require k3s_datastore_endpoint for MariaDB datastore
ansible.builtin.assert:
that:
- (k3s_datastore_endpoint | default('')) | length > 0
fail_msg: "k3s_datastore_endpoint is empty on {{ inventory_hostname }}"
tasks:
- name: Apply k3s install/config changes (server)
ansible.builtin.import_role:
name: k3s
tasks_from: install
- name: Roll out k3s config for external MariaDB datastore (agents)
hosts: k3s_hosts
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Require explicit acknowledgement (this changes the k3s datastore)
ansible.builtin.assert:
that:
- k3s_datastore_migration_ack | default(false) | bool
fail_msg: >-
Refusing to proceed without k3s_datastore_migration_ack=true.
Switching k3s datastore backends may require a rebuild/migration plan.
- name: Only run on k3s agent nodes
ansible.builtin.meta: end_host
when: k3s_role | default('') != 'agent'
tasks:
- name: Apply k3s install/config changes (agent)
ansible.builtin.import_role:
name: k3s
tasks_from: install

View File

@ -1,34 +0,0 @@
---
- name: Reset k3s nodes and reinstall with vault token
hosts: k3s_hosts
become: true
serial: 1
vars:
# During a reset we want to proactively remove the node object from the cluster
# (via a healthy control-plane host) before shutting down local k3s services.
# This prevents stale `NotReady` nodes, DaemonSet pods stuck `Terminating`, and
# `kube-node-lease` entries that can cause future joins/reinstalls to hang.
k3s_reset_force_cluster_node_delete: true
tasks:
- name: Ensure cgroup kernel params are set
ansible.builtin.import_role:
name: cgroups
- name: Remove existing k3s installation and data
ansible.builtin.import_role:
name: k3s
tasks_from: cleanup
- name: Reinstall and configure k3s
ansible.builtin.import_role:
name: k3s
- name: Refresh kubeconfig on controller after reset
hosts: k3s_hosts
gather_facts: false
run_once: true
tasks:
- name: Fetch and save kubeconfig on controller
ansible.builtin.import_role:
name: k3s
tasks_from: fetch_kubeconfig

View File

@ -1,19 +0,0 @@
---
- name: Refresh k3s server (blank slate except token/CA)
hosts: k3s_hosts
become: true
serial: 1
vars:
# Safety guard: you must also pass `-e k3s_refresh_confirm=YES`.
# Example:
# ./ansible.sh -v .vault_pass -p infrastructure/playbooks/k3s_server_refresh.yml -l myrddin.prole.org -- -e k3s_refresh_confirm=YES
k3s_refresh_run_configure: true
tasks:
- name: Ensure cgroup kernel params are set
ansible.builtin.import_role:
name: cgroups
- name: Refresh k3s server (preserve token/CA)
ansible.builtin.import_role:
name: k3s
tasks_from: refresh_server

View File

@ -1,16 +0,0 @@
---
- name: Start k3s on a single host
hosts: k3s_hosts
become: true
serial: 1
pre_tasks:
- name: Require a single host target
ansible.builtin.assert:
that:
- ansible_play_hosts_all | length == 1
fail_msg: "This playbook targets one host. Use --limit <host>."
tasks:
- name: Start k3s service
ansible.builtin.import_role:
name: k3s
tasks_from: start

View File

@ -1,33 +0,0 @@
---
- name: Stop k3s agents first
hosts: k3s_hosts
become: true
serial: 1
tasks:
- name: Stop k3s services on agent nodes
ansible.builtin.import_role:
name: k3s
tasks_from: stop
when: k3s_role != "server"
- name: Stop non-init k3s servers
hosts: k3s_hosts
become: true
serial: 1
tasks:
- name: Stop k3s services on non-init servers
ansible.builtin.import_role:
name: k3s
tasks_from: stop
when: k3s_role == "server" and not (k3s_cluster_init | bool)
- name: Stop init k3s servers last
hosts: k3s_hosts
become: true
serial: 1
tasks:
- name: Stop k3s services on init servers
ansible.builtin.import_role:
name: k3s
tasks_from: stop
when: k3s_role == "server" and (k3s_cluster_init | bool)

View File

@ -1,349 +0,0 @@
---
- name: Prepare k3s synchronization metadata
hosts: k3s_hosts
gather_facts: false
run_once: true
tasks:
- name: Initialize k3s sync source host
ansible.builtin.set_fact:
k3s_sync_source_host: ""
delegate_to: localhost
delegate_facts: true
- name: Select k3s init server as sync source
ansible.builtin.set_fact:
k3s_sync_source_host: "{{ item }}"
loop: "{{ groups['k3s_hosts'] }}"
when:
- hostvars[item].k3s_role | default('agent') == 'server'
- hostvars[item].k3s_cluster_init | default(false) | bool
delegate_to: localhost
delegate_facts: true
- name: Fallback to first k3s server as sync source
ansible.builtin.set_fact:
k3s_sync_source_host: "{{ item }}"
loop: "{{ groups['k3s_hosts'] }}"
when:
- (hostvars['localhost'].k3s_sync_source_host | length) == 0
- hostvars[item].k3s_role | default('agent') == 'server'
delegate_to: localhost
delegate_facts: true
- name: Require a k3s server as sync source
ansible.builtin.assert:
that:
- hostvars['localhost'].k3s_sync_source_host | length > 0
fail_msg: >-
No k3s sync source found. Ensure at least one host has `k3s_role: server`.
(If you have multiple servers and want to prefer the bootstrap server,
set `k3s_cluster_init: true` on exactly one server.)
- name: Ensure sync source is included in this run
ansible.builtin.assert:
that:
- hostvars['localhost'].k3s_sync_source_host in ansible_play_hosts_all
fail_msg: "Sync source {{ hostvars['localhost'].k3s_sync_source_host }} is not in this run. Include it in --limit."
- name: Create local temp directory for k3s sync
ansible.builtin.command: mktemp -d -p /tmp k3s-sync-XXXXXX
register: k3s_sync_tmpdir
changed_when: true
delegate_to: localhost
become: false
- name: Ensure k3s sync temp directory is writable
ansible.builtin.file:
path: "{{ k3s_sync_tmpdir.stdout }}"
state: directory
mode: "1777"
delegate_to: localhost
- name: Store k3s sync metadata on controller
ansible.builtin.set_fact:
k3s_sync_source_host: "{{ hostvars['localhost'].k3s_sync_source_host }}"
k3s_sync_tmpdir_path: "{{ k3s_sync_tmpdir.stdout }}"
k3s_sync_tls_bundle: "{{ k3s_sync_tmpdir.stdout }}/k3s-tls.tgz"
delegate_to: localhost
delegate_facts: true
- name: Collect k3s token and certs from init server
hosts: k3s_hosts
become: true
gather_facts: false
run_once: true
vars:
k3s_sync_source_host: "{{ hostvars['localhost'].k3s_sync_source_host | default('') }}"
k3s_sync_tls_bundle: "{{ hostvars['localhost'].k3s_sync_tls_bundle | default('') }}"
tasks:
- name: Require a k3s sync source
ansible.builtin.assert:
that:
- k3s_sync_source_host | length > 0
fail_msg: "k3s sync source is empty. Ensure the init server is reachable."
- name: Ensure k3s node token exists
ansible.builtin.stat:
path: "{{ k3s_data_dir }}/server/node-token"
register: k3s_node_token
delegate_to: "{{ k3s_sync_source_host }}"
- name: Fail when k3s node token is missing
ansible.builtin.fail:
msg: "k3s node token not found at {{ k3s_data_dir }}/server/node-token"
when: not k3s_node_token.stat.exists
- name: Read k3s node token
ansible.builtin.slurp:
src: "{{ k3s_data_dir }}/server/node-token"
register: k3s_node_token_raw
delegate_to: "{{ k3s_sync_source_host }}"
- name: Store k3s sync token on controller
ansible.builtin.set_fact:
k3s_sync_token: "{{ k3s_node_token_raw.content | b64decode | trim }}"
delegate_to: localhost
delegate_facts: true
- name: Check for k3s tls directory
ansible.builtin.stat:
path: "{{ k3s_data_dir }}/server/tls"
register: k3s_tls_dir
delegate_to: "{{ k3s_sync_source_host }}"
- name: Create k3s tls bundle
ansible.builtin.archive:
path: "{{ k3s_data_dir }}/server/tls"
dest: /tmp/k3s-tls.tgz
format: gz
when: k3s_tls_dir.stat.exists
delegate_to: "{{ k3s_sync_source_host }}"
- name: Fetch k3s tls bundle
ansible.builtin.fetch:
src: /tmp/k3s-tls.tgz
dest: "{{ k3s_sync_tls_bundle }}"
flat: true
when: k3s_tls_dir.stat.exists
delegate_to: "{{ k3s_sync_source_host }}"
- name: Mark tls bundle presence on controller
ansible.builtin.set_fact:
k3s_sync_tls_bundle_present: "{{ k3s_tls_dir.stat.exists }}"
delegate_to: localhost
delegate_facts: true
- name: Remove temporary tls bundle from source
ansible.builtin.file:
path: /tmp/k3s-tls.tgz
state: absent
when: k3s_tls_dir.stat.exists
delegate_to: "{{ k3s_sync_source_host }}"
- name: Synchronize k3s token and certs to servers
hosts: k3s_hosts
become: true
serial: 1
gather_facts: false
vars:
k3s_sync_source_host: "{{ hostvars['localhost'].k3s_sync_source_host }}"
k3s_sync_token: "{{ hostvars['localhost'].k3s_sync_token | default('') }}"
k3s_sync_tls_bundle: "{{ hostvars['localhost'].k3s_sync_tls_bundle | default('') }}"
k3s_sync_tls_bundle_present: "{{ hostvars['localhost'].k3s_sync_tls_bundle_present | default(false) }}"
pre_tasks:
- name: Require k3s sync token
ansible.builtin.assert:
that:
- k3s_sync_token | length > 0
fail_msg: "k3s sync token is empty. Check the init server token."
tasks:
- name: Stop k3s before syncing
ansible.builtin.import_role:
name: k3s
tasks_from: stop
when: inventory_hostname != k3s_sync_source_host
- name: Sync k3s token and certs
ansible.builtin.import_role:
name: k3s
tasks_from: sync
when: inventory_hostname != k3s_sync_source_host
- name: Start k3s after syncing
ansible.builtin.import_role:
name: k3s
tasks_from: start
when: inventory_hostname != k3s_sync_source_host
- name: Update k3s vault token on controller
hosts: k3s_hosts
gather_facts: false
run_once: true
vars:
k3s_sync_token: "{{ hostvars['localhost'].k3s_sync_token | default('') }}"
vault_k3s_path: "{{ playbook_dir }}/../inventory/group_vars/all/vault_k3s.yml"
vault_pass_default: "{{ playbook_dir }}/../../.vault_pass"
tasks:
- name: Skip vault update when disabled
ansible.builtin.meta: end_play
when: not (k3s_sync_update_vault | default(true) | bool)
- name: Require k3s sync token for vault update
ansible.builtin.assert:
that:
- k3s_sync_token | length > 0
fail_msg: "k3s sync token is empty. Unable to update vault."
- name: Check for default vault password file
ansible.builtin.stat:
path: "{{ vault_pass_default }}"
register: vault_pass_default_stat
delegate_to: localhost
- name: Determine vault password file
ansible.builtin.set_fact:
k3s_vault_password_file: >-
{{ k3s_vault_password_file
| default(lookup('env', 'ANSIBLE_VAULT_PASSWORD_FILE') | default('', true), true) }}
delegate_to: localhost
delegate_facts: true
- name: Fallback to default vault password file
ansible.builtin.set_fact:
k3s_vault_password_file: "{{ vault_pass_default }}"
when:
- (hostvars['localhost'].k3s_vault_password_file | default('')) | length == 0
- vault_pass_default_stat.stat.exists
delegate_to: localhost
delegate_facts: true
- name: Require vault password file
ansible.builtin.assert:
that:
- (hostvars['localhost'].k3s_vault_password_file | default('')) | length > 0
fail_msg: "Set k3s_vault_password_file or ANSIBLE_VAULT_PASSWORD_FILE to update vault."
- name: Ensure vault file exists
ansible.builtin.stat:
path: "{{ vault_k3s_path }}"
register: vault_k3s_file
delegate_to: localhost
- name: Fail when vault file is missing
ansible.builtin.fail:
msg: "Vault file not found at {{ vault_k3s_path }}"
when: not vault_k3s_file.stat.exists
- name: Check if vault file is encrypted
ansible.builtin.command: "head -n 1 {{ vault_k3s_path }}"
register: vault_k3s_head
changed_when: false
delegate_to: localhost
- name: Mark vault encryption state
ansible.builtin.set_fact:
vault_k3s_encrypted: "{{ (vault_k3s_head.stdout | default('')) is search('^\\$ANSIBLE_VAULT') }}"
delegate_to: localhost
delegate_facts: true
- name: Decrypt vault_k3s.yml
ansible.builtin.command: >-
ansible-vault decrypt {{ vault_k3s_path }}
--vault-password-file {{ hostvars['localhost'].k3s_vault_password_file }}
changed_when: true
delegate_to: localhost
when: hostvars['localhost'].vault_k3s_encrypted | default(false)
- name: Update vault k3s token
ansible.builtin.lineinfile:
path: "{{ vault_k3s_path }}"
regexp: '^vault_k3s_token:'
line: "vault_k3s_token: \"{{ k3s_sync_token }}\""
delegate_to: localhost
- name: Encrypt vault_k3s.yml
ansible.builtin.command: >-
ansible-vault encrypt {{ vault_k3s_path }}
--vault-password-file {{ hostvars['localhost'].k3s_vault_password_file }}
changed_when: true
delegate_to: localhost
when: hostvars['localhost'].vault_k3s_encrypted | default(false)
- name: Refresh kubeconfig on controller
hosts: k3s_hosts
gather_facts: false
run_once: true
tasks:
- name: Fetch and save kubeconfig on controller
ansible.builtin.import_role:
name: k3s
tasks_from: fetch_kubeconfig
- name: Validate k3s nodes from controller
hosts: k3s_hosts
gather_facts: false
run_once: true
vars:
k3s_kubeconfig_service_path: "{{ playbook_dir }}/../../etc/secrets/k3s.kubeconfig"
k3s_kubeconfig_project_path: "{{ playbook_dir }}/../../prole-k3s.kubeconfig"
k3s_expected_node_count: "{{ groups['k3s_hosts'] | length }}"
tasks:
- name: Check for service kubeconfig
ansible.builtin.stat:
path: "{{ k3s_kubeconfig_service_path }}"
register: _k3s_kubeconfig_service_stat
delegate_to: localhost
become: false
- name: Check for project kubeconfig
ansible.builtin.stat:
path: "{{ k3s_kubeconfig_project_path }}"
register: _k3s_kubeconfig_project_stat
delegate_to: localhost
become: false
- name: Select kubeconfig path for validation
ansible.builtin.set_fact:
k3s_kubeconfig_validation_path: >-
{{ k3s_kubeconfig_service_path
if _k3s_kubeconfig_service_stat.stat.exists
else k3s_kubeconfig_project_path }}
changed_when: false
- name: Require a kubeconfig for validation
ansible.builtin.assert:
that:
- _k3s_kubeconfig_service_stat.stat.exists or _k3s_kubeconfig_project_stat.stat.exists
fail_msg: >-
No kubeconfig found at {{ k3s_kubeconfig_service_path }} or {{ k3s_kubeconfig_project_path }}.
Run `k3s_reset` or `k3s_sync` again and ensure the init server is reachable.
- name: Show nodes (kubectl get nodes -o wide)
ansible.builtin.command: "kubectl --kubeconfig {{ k3s_kubeconfig_validation_path }} get nodes -o wide"
register: _k3s_nodes_wide
changed_when: false
delegate_to: localhost
become: false
- name: Debug nodes output
ansible.builtin.debug:
var: _k3s_nodes_wide.stdout_lines
- name: Count nodes
ansible.builtin.shell: |
set -euo pipefail
kubectl --kubeconfig "{{ k3s_kubeconfig_validation_path }}" get nodes --no-headers | wc -l | tr -d ' '
args:
executable: /bin/bash
register: _k3s_node_count
changed_when: false
delegate_to: localhost
become: false
- name: Require expected node count
ansible.builtin.assert:
that:
- (_k3s_node_count.stdout | int) >= (k3s_expected_node_count | int)
fail_msg: >-
Expected at least {{ k3s_expected_node_count }} nodes, but kubectl reported {{ _k3s_node_count.stdout | default('0') }}.
Ensure all `k3s_hosts` are reachable and have rejoined, then re-run `k3s_sync`.

View File

@ -1,12 +0,0 @@
---
- 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

View File

@ -1,12 +0,0 @@
---
- 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

View File

@ -1,15 +0,0 @@
---
- name: Provision Merlin MariaDB primary (users/grants/datadir)
hosts: mariadb_primary
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- mariadb_primary

View File

@ -1,79 +0,0 @@
---
- name: Migrate /prole/logs to iSCSI
hosts: myrddin.prole.org
become: true
vars:
prole_logs_iscsi_target:
iqn: "iqn.2000-01.com.synology:synology.Target-1.292d45194a1"
chap_user: "prole"
chap_password: "{{ vault_iscsi_prole_password }}"
mounts:
- path: /prole/logs/synology
fstype: ext4
opts: "_netdev,noatime"
src: "UUID=5335affd-b1ab-4134-a1c0-be88ab965309"
tasks:
- name: Ensure rsync installed
ansible.builtin.package:
name: rsync
state: present
- name: Ensure staging mountpoint exists
ansible.builtin.file:
path: /prole/logs/synology
state: directory
mode: "0755"
- name: Login and mount logs volume at staging path
ansible.builtin.include_role:
name: iscsi
vars:
iscsi_targets: "{{ [prole_logs_iscsi_target] }}"
iscsi_absent_mounts: []
- name: Initial rsync from /prole/logs to staging
ansible.builtin.command: >
rsync -aHAX --info=stats2 /prole/logs/ /prole/logs/synology/
register: rsync_initial
failed_when: rsync_initial.rc not in [0, 24]
- name: Stop rsyslog
ansible.builtin.service:
name: rsyslog
state: stopped
- name: Final rsync after rsyslog stop
ansible.builtin.command: >
rsync -aHAX --delete --info=stats2 /prole/logs/ /prole/logs/synology/
register: rsync_final
failed_when: rsync_final.rc not in [0, 24]
- name: Unmount staging logs volume
ansible.builtin.mount:
path: /prole/logs/synology
state: unmounted
- name: Remove staging fstab entry
ansible.builtin.lineinfile:
path: /etc/fstab
state: absent
regexp: '^\\S+\\s+/prole/logs/synology\\s+'
- name: Ensure /prole/logs exists
ansible.builtin.file:
path: /prole/logs
state: directory
mode: "0755"
- name: Mount logs volume at /prole/logs
ansible.builtin.mount:
path: /prole/logs
src: "UUID=5335affd-b1ab-4134-a1c0-be88ab965309"
fstype: ext4
opts: "_netdev,noatime"
state: mounted
- name: Start rsyslog
ansible.builtin.service:
name: rsyslog
state: started

View File

@ -1,7 +0,0 @@
---
- name: Rotate Pi-hole FTL database safely
hosts: pihole
become: true
serial: 1
roles:
- pihole_db_rotate

View File

@ -1,232 +0,0 @@
---
# Runbook tiers (systemd-like startup ordering):
# 1) boot / kernel
# 2) storage: iscsi auth, iscsi login
# 3) filesystem mount
# 4) OS configuration (e.g., samba)
# 5) OS package installation (k3s)
# 6) OS package configuration (helm/kubectl apply)
#
# This playbook is intentionally ordered so each tier fully completes before
# moving on to the next tier.
- name: Tier 1 - Boot / kernel (cgroups)
hosts: k3s_hosts
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- cgroups
- name: Tier 1.5 - Quiesce k3s and detach migrated iSCSI LUNs (myrddin)
hosts: myrddin.prole.org
gather_facts: false
become: true
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
tasks:
- name: Check if k3s systemd unit exists
ansible.builtin.stat:
path: /etc/systemd/system/k3s.service
register: _k3s_unit
- name: Stop k3s (quiesce before iSCSI detach)
ansible.builtin.systemd:
name: k3s
state: stopped
when:
- not ansible_check_mode
- _k3s_unit.stat.exists | default(false)
- (iscsi_absent_mounts | default([]) | length) > 0 or (iscsi_absent_targets | default([]) | length) > 0
- name: Detach migrated iSCSI targets/mounts (best-effort cleanup)
ansible.builtin.import_role:
name: iscsi
tasks_from: detach
when:
- (iscsi_absent_mounts | default([]) | length) > 0 or (iscsi_absent_targets | default([]) | length) > 0
- name: Tier 2 - Storage (iSCSI auth/login)
hosts: iscsi
gather_facts: false
become: true
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
tasks:
- name: iSCSI auth/login
ansible.builtin.import_role:
name: iscsi
tasks_from: login
- name: Tier 3 - Filesystem mounts (iSCSI)
hosts: iscsi
gather_facts: false
become: true
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
tasks:
- name: iSCSI mounts
ansible.builtin.import_role:
name: iscsi
tasks_from: mount
- name: Tier 4 - OS configuration (Pi-hole DNS and resilience)
hosts: pihole
gather_facts: false
become: true
tags:
- pihole
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- pihole_dns
- name: Tier 4 - OS configuration (Samba AD DNS reverse zones and PTRs)
hosts: ad_dc
gather_facts: false
become: true
tags:
- samba
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- samba_ad_dc
- samba_dns
- samba_reverse_dns
- name: Tier 4 - OS configuration (base linux)
hosts: linux_hosts
gather_facts: false
become: true
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- role: netplan_static
when: netplan_static_enabled | default(false) | bool
- local_user
- rsyslog
- prole
- name: Tier 4.1 - OS configuration (Prole SSL certs)
hosts: ssl_hosts
gather_facts: false
become: true
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- prole_ssl
- name: Tier 4 - OS configuration (console dashboard)
hosts: k3s_hosts
gather_facts: false
become: true
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- dashboard
- name: Tier 4.5 - Database (MariaDB primary)
hosts: mariadb_primary
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- mariadb_primary
- name: Tier 4.6 - Database (MariaDB replica)
hosts: mariadb_replica
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
roles:
- mariadb_replica
- name: Tier 5 - OS package installation (k3s)
hosts: k3s_hosts
gather_facts: false
become: true
serial: 1
tags: [k3s]
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
tasks:
- name: Install and start k3s
ansible.builtin.import_role:
name: k3s
tasks_from: install
- name: Tier 5.5 - Kubernetes node operations (labels)
hosts: k3s_servers
gather_facts: false
become: true
serial: 1
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
tasks:
- name: Persist Kubernetes node labels (post-provisioning)
ansible.builtin.import_role:
name: k3s
tasks_from: label_nodes
when: (k3s_node_labels | default({}) | length) > 0
- name: Tier 6 - OS package configuration (helm/kubectl apply)
hosts: k3s_hosts
gather_facts: false
become: true
serial: 1
tags: [k3s]
pre_tasks:
- name: Gather minimal facts
ansible.builtin.setup:
gather_subset:
- min
tasks:
- name: Configure k3s add-ons
ansible.builtin.import_role:
name: k3s
tasks_from: configure

View File

@ -1,7 +0,0 @@
---
- name: Smoke test connectivity (no roles)
hosts: all
gather_facts: false
tasks:
- name: Ping
ansible.builtin.ping:

View File

@ -1,12 +0,0 @@
---
- 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

View File

@ -1,12 +0,0 @@
---
- 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

View File

@ -1,12 +0,0 @@
---
- 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

View File

@ -1,12 +0,0 @@
---
- 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

View File

@ -1,6 +0,0 @@
---
- name: Configure swap on Raspberry Pi hosts
hosts: raspberry.prole.org
become: true
roles:
- swap

View File

@ -1,37 +0,0 @@
---
- name: Fetch gold Pi-hole config from pi.prole.org
hosts: pi.prole.org
become: true
gather_facts: false
tasks:
- name: Fetch /etc/pihole/pihole.toml to control node
ansible.builtin.fetch:
src: /etc/pihole/pihole.toml
dest: /tmp/pihole.toml.gold
flat: true
# - name: Show diff between current and gold
# ansible.builtin.command: diff -u /etc/pihole/pihole.toml /tmp/pihole.toml.gold
# register: toml_diff
# failed_when: false
# changed_when: false
- name: Apply gold Pi-hole config to raspberry.prole.org
hosts: raspberry.prole.org
become: true
gather_facts: false
tasks:
- name: Copy gold pihole.toml to raspberry
ansible.builtin.copy:
src: /tmp/pihole.toml.gold
dest: /etc/pihole/pihole.toml
owner: root
group: root
mode: "0644"
register: toml_copy
- name: Restart Pi-hole FTL if config changed
ansible.builtin.systemd:
name: pihole-FTL
state: restarted
when: toml_copy.changed

View File

@ -1,77 +0,0 @@
---
- name: Test - k3s kubeconfig server rewrite (local)
hosts: localhost
connection: local
gather_facts: false
vars:
_sample_kubeconfig: |
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZQ==
server: https://127.0.0.1:6443
name: default
contexts:
- context:
cluster: default
user: default
name: default
current-context: default
kind: Config
users:
- name: default
user:
client-certificate-data: ZmFrZQ==
client-key-data: ZmFrZQ==
_desired_server: https://myrddin.prole.org:6443
tasks:
- name: Old kubeconfig rewrite regex should break YAML (expected)
block:
- name: Apply old buggy regex
ansible.builtin.set_fact:
_kubeconfig_bad: >-
{{ _sample_kubeconfig
| regex_replace('server: https://[^\\s]+', 'server: ' ~ _desired_server) }}
- name: Attempt YAML parse (expected to fail)
ansible.builtin.set_fact:
_kubeconfig_bad_parsed: "{{ _kubeconfig_bad | from_yaml }}"
- name: Fail if YAML parse unexpectedly succeeded
ansible.builtin.fail:
msg: "Old regex unexpectedly produced valid YAML; test sample may need adjustment."
rescue:
- name: Mark expected failure as observed
ansible.builtin.set_fact:
_old_regex_failed_as_expected: true
always:
- name: Assert old regex failure was observed
ansible.builtin.assert:
that:
- _old_regex_failed_as_expected | default(false) | bool
- name: New kubeconfig rewrite regex should preserve YAML structure
ansible.builtin.set_fact:
_kubeconfig_good: >-
{{ _sample_kubeconfig
| regex_replace('server:\s*https?://\S+', 'server: ' ~ _desired_server) }}
- name: Parse rewritten kubeconfig
ansible.builtin.set_fact:
_kubeconfig_good_parsed: "{{ _kubeconfig_good | from_yaml }}"
changed_when: false
- name: Assert kubeconfig keys and server were rewritten
ansible.builtin.assert:
that:
- _kubeconfig_good_parsed is mapping
- (_kubeconfig_good_parsed.contexts | default([])) | length == 1
- (_kubeconfig_good_parsed.clusters | default([])) | length == 1
- (_kubeconfig_good_parsed.users | default([])) | length == 1
- _kubeconfig_good_parsed['current-context'] == 'default'
- _kubeconfig_good_parsed.clusters[0].cluster.server == _desired_server

View File

@ -1,35 +0,0 @@
---
- name: Test - k3s argument validation (local)
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Validation should fail when a known-bad arg is present (etcd-timeout)
block:
- name: Run validation with a bad arg (expected to fail)
ansible.builtin.include_role:
name: k3s
tasks_from: validate_args
vars:
k3s_kube_apiserver_args:
- "etcd-timeout=50s"
rescue:
- name: Mark expected failure as observed
ansible.builtin.set_fact:
_bad_arg_validation_failed: true
always:
- name: Assert the bad-arg validation failed as expected
ansible.builtin.assert:
that:
- _bad_arg_validation_failed | default(false) | bool
- name: Validation should pass with supported args
ansible.builtin.include_role:
name: k3s
tasks_from: validate_args
vars:
k3s_kube_apiserver_args:
- "request-timeout=10s"
- "min-request-timeout=10"

View File

@ -1,9 +0,0 @@
---
- hosts: myrddin.prole.org
become: true
tasks:
- name: Ensure OpenBao storage directory exists
ansible.builtin.file:
path: /synology/d001/openbao
state: directory
mode: "0777" # OpenBao pod runs as non-root usually, but k3s local-path often needs this or specific UID

View File

@ -1,12 +0,0 @@
---
- hosts: myrddin.prole.org
become: true
tasks:
- name: Try to mount all filesystems
ansible.builtin.command: mount -a
- name: Check if required mount is present
ansible.builtin.command: findmnt -n /opt/prole/logs/chrisfu
register: mount_check
failed_when: false
- debug:
var: mount_check.stdout

View File

@ -1,46 +0,0 @@
---
prole_audit_scan_paths:
- /etc
- /usr/local
- /opt
- /srv
prole_audit_scan_paths_extra: []
prole_audit_allow_prefixes:
- "{{ prole_home | default('/opt/prole') }}"
- "{{ prole_conf | default('/opt/prole/conf') }}"
- /etc/rancher/k3s
- /var/lib/rancher
- /usr/local/bin/k3s
- /usr/local/bin/kubectl
- /usr/local/bin/crictl
- /usr/local/bin/ctr
- /usr/local/bin/k3s-killall.sh
- /usr/local/bin/k3s-uninstall.sh
- /etc/systemd/system/k3s.service
- /etc/systemd/system/k3s.service.env
- /etc/systemd/system/k3s-agent.service
- /etc/systemd/system/k3s-agent.service.env
- /usr/local/bin/helm
- /etc/rsyslog.d/10-server.conf
- /etc/rsyslog.d/90-forward-all.conf
- /etc/dnsmasq.d/05-samba-ad.conf
- /etc/dnsmasq.d/99-dns-forward-max.conf
- /usr/local/sbin/rotate-pihole-db
- /var/lib/pihole/db-archive
prole_audit_allow_prefixes_extra: []
prole_audit_dpkg_verify_packages: []
prole_audit_tracked_packages:
- curl
- iptables
- rsyslog
- samba
- krb5-user
- open-iscsi
- util-linux
- dphys-swapfile
- zram-tools

View File

@ -1,204 +0,0 @@
---
- name: Build audit allowlists
ansible.builtin.set_fact:
prole_audit_scan_paths_combined: >-
{{ (prole_audit_scan_paths | default([]))
+ (prole_audit_scan_paths_extra | default([]))
| unique }}
prole_audit_allow_prefixes_combined: >-
{{ (prole_audit_allow_prefixes | default([]))
+ (prole_audit_allow_prefixes_extra | default([]))
| unique }}
changed_when: false
- name: Verify dpkg database (Debian only)
ansible.builtin.command: >-
dpkg -V {{ prole_audit_dpkg_verify_packages | default([]) | join(' ') }}
register: prole_audit_dpkg_verify
changed_when: false
failed_when: false
check_mode: false
when: ansible_facts.os_family == "Debian"
- name: List files not owned by dpkg and not managed by prole
ansible.builtin.shell:
cmd: |
set -euo pipefail
paths=({{ prole_audit_scan_paths_combined | map('quote') | join(' ') }})
ALLOW_PREFIXES="$(printf '%s\n' {{ prole_audit_allow_prefixes_combined | map('quote') | join(' ') }})"
export ALLOW_PREFIXES
valid_paths=()
for p in "${paths[@]}"; do
if [ -e "$p" ]; then
valid_paths+=("$p")
fi
done
if [ "${#valid_paths[@]}" -eq 0 ]; then
exit 0
fi
{
find "${valid_paths[@]}" -xdev \( -type f -o -type l \) -print0 |
awk -v RS='\0' '
BEGIN { n = split(ENVIRON["ALLOW_PREFIXES"], a, "\n") }
{
allowed = 0
for (i = 1; i <= n; i++) {
p = a[i]
if (p != "" && index($0, p) == 1) {
if (length($0) == length(p) || substr($0, length(p) + 1, 1) == "/") {
allowed = 1
break
}
}
}
if (!allowed) { printf "%s\0", $0 }
}' |
xargs -0 -r -n 200 dpkg -S 2>&1 >/dev/null || true
} | sed -n 's/^dpkg-query: no path found matching pattern //p' | sort -u
args:
executable: /bin/bash
register: prole_audit_unowned
changed_when: false
check_mode: false
when:
- ansible_facts.os_family == "Debian"
- prole_audit_scan_paths_combined | length > 0
- name: Check k3s binary
ansible.builtin.stat:
path: /usr/local/bin/k3s
register: prole_audit_k3s_stat
changed_when: false
check_mode: false
- name: Read k3s version
ansible.builtin.command: /usr/local/bin/k3s --version
register: prole_audit_k3s_version
changed_when: false
failed_when: false
check_mode: false
when: prole_audit_k3s_stat.stat.exists
- name: Hash k3s binary
ansible.builtin.command: sha256sum /usr/local/bin/k3s
register: prole_audit_k3s_sha
changed_when: false
check_mode: false
when: prole_audit_k3s_stat.stat.exists
- name: Check k3s dpkg owner
ansible.builtin.command: dpkg -S /usr/local/bin/k3s
register: prole_audit_k3s_owner
changed_when: false
failed_when: false
check_mode: false
when: prole_audit_k3s_stat.stat.exists and ansible_facts.os_family == "Debian"
- name: Build k3s provenance
ansible.builtin.set_fact:
prole_audit_k3s_provenance:
path: /usr/local/bin/k3s
exists: "{{ prole_audit_k3s_stat.stat.exists | default(false) }}"
sha256: "{{ (prole_audit_k3s_sha.stdout | default('')) | regex_replace('\\s+.*$', '') }}"
version: "{{ prole_audit_k3s_version.stdout | default('') }}"
dpkg_owner: >-
{{ (prole_audit_k3s_owner.stdout | default('')) if (prole_audit_k3s_owner is defined and prole_audit_k3s_owner.rc == 0)
else 'unowned' }}
changed_when: false
- name: Locate helm binary
ansible.builtin.shell: command -v helm
register: prole_audit_helm_path
changed_when: false
failed_when: false
check_mode: false
- name: Read helm version
ansible.builtin.command:
argv:
- "{{ prole_audit_helm_path.stdout | trim }}"
- version
- --short
register: prole_audit_helm_version
changed_when: false
failed_when: false
check_mode: false
when: prole_audit_helm_path.rc == 0
- name: Hash helm binary
ansible.builtin.command: sha256sum "{{ prole_audit_helm_path.stdout | trim }}"
register: prole_audit_helm_sha
changed_when: false
check_mode: false
when: prole_audit_helm_path.rc == 0
- name: Check helm dpkg owner
ansible.builtin.command: dpkg -S "{{ prole_audit_helm_path.stdout | trim }}"
register: prole_audit_helm_owner
changed_when: false
failed_when: false
check_mode: false
when: prole_audit_helm_path.rc == 0 and ansible_facts.os_family == "Debian"
- name: Build helm provenance
ansible.builtin.set_fact:
prole_audit_helm_provenance:
path: "{{ prole_audit_helm_path.stdout | default('') | trim }}"
exists: "{{ prole_audit_helm_path.rc == 0 }}"
sha256: "{{ (prole_audit_helm_sha.stdout | default('')) | regex_replace('\\s+.*$', '') }}"
version: "{{ prole_audit_helm_version.stdout | default('') }}"
dpkg_owner: >-
{{ (prole_audit_helm_owner.stdout | default('')) if (prole_audit_helm_owner is defined and prole_audit_helm_owner.rc == 0)
else 'unowned' }}
changed_when: false
- name: Query tracked package versions
ansible.builtin.shell: >-
dpkg-query -W -f='${Package} ${Version} ${Architecture}\n'
{{ prole_audit_tracked_packages | default([]) | join(' ') }}
register: prole_audit_tracked_pkgs
changed_when: false
failed_when: false
check_mode: false
when:
- ansible_facts.os_family == "Debian"
- prole_audit_tracked_packages | default([]) | length > 0
- name: Report dpkg verification changes
ansible.builtin.debug:
msg: >-
{{ ((prole_audit_dpkg_verify.stdout_lines | default([]))
+ (prole_audit_dpkg_verify.stderr_lines | default([])))
if ((prole_audit_dpkg_verify.stdout_lines | default([]) | length) > 0
or (prole_audit_dpkg_verify.stderr_lines | default([]) | length) > 0)
else ['dpkg -V returned no differences'] }}
when: prole_audit_dpkg_verify is defined
- name: Report files not owned by dpkg and not managed by prole
ansible.builtin.debug:
msg: >-
{{ (prole_audit_unowned.stdout_lines | default([]))
if (prole_audit_unowned.stdout_lines | default([]) | length) > 0
else ['no unowned files found in scan paths'] }}
when: prole_audit_unowned is defined
- name: Report tracked package versions
ansible.builtin.debug:
msg: >-
{{ ((prole_audit_tracked_pkgs.stdout_lines | default([]))
+ (prole_audit_tracked_pkgs.stderr_lines | default([])))
if ((prole_audit_tracked_pkgs.stdout_lines | default([]) | length) > 0
or (prole_audit_tracked_pkgs.stderr_lines | default([]) | length) > 0)
else ['no tracked packages reported'] }}
when: prole_audit_tracked_pkgs is defined
- name: Report k3s provenance
ansible.builtin.debug:
var: prole_audit_k3s_provenance
- name: Report helm provenance
ansible.builtin.debug:
var: prole_audit_helm_provenance

View File

@ -1,13 +0,0 @@
---
cgroups_cmdline_candidates:
- /boot/firmware/cmdline.txt
- /boot/cmdline.txt
cgroups_required_params:
- cgroup_memory=1
- cgroup_enable=memory
cgroups_remove_params:
- cgroup_disable=memory
cgroups_reboot: true

View File

@ -1,196 +0,0 @@
---
- name: Find kernel cmdline file
ansible.builtin.stat:
path: "{{ item }}"
loop: "{{ cgroups_cmdline_candidates }}"
register: cmdline_stats
- name: Select kernel cmdline path
ansible.builtin.set_fact:
cgroups_cmdline_all_paths: "{{ cmdline_stats.results | selectattr('stat.exists') | map(attribute='stat.path') | list }}"
- name: Select kernel cmdline update targets
ansible.builtin.set_fact:
cgroups_cmdline_paths: "{{ cgroups_cmdline_all_paths }}"
- name: Fail when kernel cmdline file is missing
ansible.builtin.fail:
msg: "No kernel cmdline file found. Checked: {{ cgroups_cmdline_candidates | join(', ') }}"
when: cgroups_cmdline_paths | length == 0
- name: Remove literal backslash escapes from kernel cmdline files
ansible.builtin.replace:
path: "{{ item }}"
regexp: "\\\\[nrt]"
replace: " "
loop: "{{ cgroups_cmdline_paths }}"
register: cmdline_escape_cleanup
- name: Remove stray backslashes from kernel cmdline files
ansible.builtin.replace:
path: "{{ item }}"
regexp: "\\\\"
replace: " "
loop: "{{ cgroups_cmdline_paths }}"
register: cmdline_backslash_cleanup
- name: Read kernel cmdline files
ansible.builtin.slurp:
path: "{{ item }}"
loop: "{{ cgroups_cmdline_paths }}"
register: cmdline_files
- name: Read current /proc/cmdline
ansible.builtin.command: "cat /proc/cmdline"
register: proc_cmdline_current
changed_when: false
check_mode: no
- name: Check current cgroup params in /proc/cmdline
ansible.builtin.set_fact:
cgroups_missing_in_proc: "{{ cgroups_required_params | reject('in', proc_cmdline_current.stdout | default('')) | list }}"
cgroups_conflicting_in_proc: "{{ cgroups_remove_params | select('in', proc_cmdline_current.stdout | default('')) | list }}"
- name: Normalize kernel cmdline content
ansible.builtin.set_fact:
cgroups_cmdline_files: >-
{{ (cgroups_cmdline_files | default([])) + [{
'path': item.item,
'raw_line': ((item.content | b64decode).splitlines() | join(' ') | trim),
'sanitized': (((item.content | b64decode).splitlines() | join(' ') | trim)
| regex_replace('\\\\[nrt]', ' ')
| regex_replace('\\\\', ' ')
| regex_replace('\\s+', ' ')
| trim)
}] }}
loop: "{{ cmdline_files.results }}"
- name: Select kernel cmdline line
ansible.builtin.set_fact:
cgroups_cmdline_current: "{{ (cgroups_cmdline_files | map(attribute='sanitized') | list | first) | default('') }}"
- name: Select cmdline source
ansible.builtin.set_fact:
cgroups_cmdline_source: "{{ (cgroups_cmdline_current | length > 0) | ternary(cgroups_cmdline_current, proc_cmdline_current.stdout | default('')) }}"
- name: Sanitize kernel cmdline
ansible.builtin.set_fact:
cgroups_cmdline_sanitized: >-
{{ cgroups_cmdline_source
| regex_replace('\\\\[nrt]', ' ')
| regex_replace('\\\\', ' ')
| regex_replace('\\s+', ' ')
| trim }}
- name: Tokenize kernel cmdline
ansible.builtin.set_fact:
cgroups_cmdline_tokens: "{{ cgroups_cmdline_sanitized.split() }}"
- name: Remove conflicting cgroup params
ansible.builtin.set_fact:
cgroups_cmdline_filtered: "{{ cgroups_cmdline_tokens | reject('in', cgroups_remove_params) | list }}"
- name: Calculate missing cgroup params
ansible.builtin.set_fact:
cgroups_missing_params: "{{ cgroups_required_params | reject('in', cgroups_cmdline_filtered) | list }}"
- name: Build updated kernel cmdline tokens
ansible.builtin.set_fact:
cgroups_cmdline_new_tokens: "{{ (cgroups_cmdline_filtered + cgroups_missing_params) | unique | list }}"
- name: Build updated kernel cmdline line
ansible.builtin.set_fact:
cgroups_cmdline_new: "{{ cgroups_cmdline_new_tokens | join(' ') }}"
- name: Sanitize updated kernel cmdline line
ansible.builtin.set_fact:
cgroups_cmdline_new: >-
{{ cgroups_cmdline_new
| regex_replace('\\\\[nrt]', ' ')
| regex_replace('\\\\', ' ')
| regex_replace('\\s+', ' ')
| trim }}
- name: Determine if kernel cmdline needs update
ansible.builtin.set_fact:
cgroups_cmdline_needs_update: >-
{{ cgroups_cmdline_new != cgroups_cmdline_sanitized or
(cgroups_cmdline_paths | length) > 1 }}
- name: Update kernel cmdline when params are missing
ansible.builtin.copy:
dest: "{{ item.path }}"
content: >-
{{ cgroups_cmdline_new ~ '\n' }}
mode: "0644"
register: cmdline_update
when: item.raw_line != cgroups_cmdline_new
loop: "{{ cgroups_cmdline_files }}"
- name: Remove literal backslash escapes from updated cmdline files
ansible.builtin.replace:
path: "{{ item }}"
regexp: "\\\\[nrt]"
replace: " "
loop: "{{ cgroups_cmdline_paths }}"
when: (cmdline_update.results | default([]) | selectattr('changed') | list | length) > 0
- name: Normalize updated cmdline whitespace
ansible.builtin.replace:
path: "{{ item }}"
regexp: "\\s+"
replace: " "
loop: "{{ cgroups_cmdline_paths }}"
when: (cmdline_update.results | default([]) | selectattr('changed') | list | length) > 0
- name: Determine if kernel cmdline was updated
ansible.builtin.set_fact:
cgroups_cmdline_changed: >-
{{ (cmdline_escape_cleanup.results | default([]) | selectattr('changed') | list | length) > 0 or
(cmdline_backslash_cleanup.results | default([]) | selectattr('changed') | list | length) > 0 or
(cmdline_update.results | default([]) | selectattr('changed') | list | length) > 0 }}
- name: Determine if reboot is required
ansible.builtin.set_fact:
cgroups_reboot_needed: >-
{{ cgroups_cmdline_changed | bool or
(cgroups_missing_in_proc | default([]) | length) > 0 }}
- name: Reboot to apply cgroup settings
ansible.builtin.reboot:
msg: "Rebooting to apply cgroup kernel parameters"
pre_reboot_delay: 3
reboot_timeout: 600
when: cgroups_reboot_needed | bool and cgroups_reboot | bool
- name: Verify cgroup kernel parameters after reboot
ansible.builtin.command: "cat /proc/cmdline"
register: proc_cmdline
changed_when: false
check_mode: no
- name: Read cgroup v2 controllers
ansible.builtin.command: "cat /sys/fs/cgroup/cgroup.controllers"
register: cgroup_controllers
changed_when: false
check_mode: no
- name: Record cgroup memory controller presence
ansible.builtin.set_fact:
cgroups_memory_controller_present: "{{ 'memory' in (cgroup_controllers.stdout | default('')) }}"
- name: Fail when cgroup params are missing (reboot required)
ansible.builtin.fail:
msg: "Missing cgroup params in /proc/cmdline: {{ cgroups_required_params | reject('in', proc_cmdline.stdout | default('')) | list }}. Reboot required."
when:
- not ansible_check_mode
- (cgroups_required_params | reject('in', proc_cmdline.stdout | default('')) | list) | length > 0
- not cgroups_memory_controller_present
- name: Fail when conflicting cgroup params are present
ansible.builtin.fail:
msg: "Conflicting cgroup params in /proc/cmdline: {{ cgroups_remove_params | select('in', proc_cmdline.stdout | default('')) | list }}. Reboot required."
when:
- not ansible_check_mode
- (cgroups_remove_params | select('in', proc_cmdline.stdout | default('')) | list) | length > 0
- not cgroups_memory_controller_present

View File

@ -1,23 +0,0 @@
---
# Local console dashboard (X + conky)
dashboard_user: pi
dashboard_script_dest: /usr/local/bin/dashboard.sh
# Primary display (Linux VT) to use for the dashboard session.
dashboard_vt: 1
# Whether to enable the service at boot. (Starting it manually is enough.)
dashboard_enable: false
# Start the service as part of the playbook run.
dashboard_start: false
# Debian-family package list (kept explicit for reproducibility).
dashboard_packages_debian:
# `conky` is a virtual package on some Debian releases; install a concrete provider.
- conky-all
- fonts-dejavu-core
- x11-xserver-utils
- xinit
- xserver-xorg

View File

@ -1,4 +0,0 @@
---
- name: reload systemd
ansible.builtin.command: systemctl daemon-reload
changed_when: false

View File

@ -1,41 +0,0 @@
---
- name: Install dashboard dependencies (Debian)
ansible.builtin.apt:
name: "{{ dashboard_packages | default(dashboard_packages_debian) }}"
state: present
update_cache: true
when: ansible_facts.os_family == 'Debian'
- name: Fail on unsupported OS family
ansible.builtin.fail:
msg: "dashboard role currently supports Debian-family hosts only (got os_family={{ ansible_facts.os_family }})."
when: ansible_facts.os_family != 'Debian'
- name: Deploy dashboard script
ansible.builtin.copy:
src: "{{ playbook_dir }}/../../tools/dashboard.sh"
dest: "{{ dashboard_script_dest }}"
owner: root
group: root
mode: '0755'
- name: Deploy systemd unit
ansible.builtin.template:
src: dashboard.service.j2
dest: /etc/systemd/system/dashboard.service
owner: root
group: root
mode: '0644'
notify: reload systemd
- name: Enable dashboard service
ansible.builtin.systemd:
name: dashboard
enabled: "{{ dashboard_enable | bool }}"
- name: Start dashboard service (optional)
ansible.builtin.systemd:
name: dashboard
state: started
when: dashboard_start | bool

View File

@ -1,40 +0,0 @@
[Unit]
Description=Prole console dashboard (X + conky)
After=systemd-user-sessions.service
Wants=systemd-user-sessions.service
# If a graphical desktop is running, starting this service will stop it.
Conflicts=graphical.target
Before=graphical.target
# If a getty is running on the dashboard VT, stop it so Xorg can take over.
Conflicts=getty@tty{{ dashboard_vt }}.service
[Service]
Type=simple
# Attach the service to the dashboard VT so Xorg can take over the display.
TTYPath=/dev/tty{{ dashboard_vt }}
StandardInput=tty
StandardOutput=journal+console
StandardError=journal+console
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
Environment=DASHBOARD_VT={{ dashboard_vt }}
ExecStart={{ dashboard_script_dest }}
# When the dashboard stops, try to restore the login prompt on that VT.
ExecStopPost=-/usr/bin/systemctl --no-block start getty@tty{{ dashboard_vt }}.service
Restart=always
RestartSec=2
# Ensure X/conky are terminated when the service stops.
KillMode=control-group
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target

View File

@ -1,19 +0,0 @@
---
- name: Verify dashboard role defaults are installable on Debian (no virtual packages)
hosts: localhost
connection: local
gather_facts: false
tasks:
- name: Load role defaults
ansible.builtin.include_vars:
file: "{{ playbook_dir }}/../defaults/main.yml"
- name: Ensure conky package is a concrete provider (not the virtual package)
ansible.builtin.assert:
that:
- "'conky' not in dashboard_packages_debian"
- "'conky-all' in dashboard_packages_debian"
fail_msg: >-
dashboard_packages_debian must not include the virtual 'conky' package.
Use a concrete provider such as 'conky-all', 'conky-std', or 'conky-cli'.

View File

@ -1,10 +0,0 @@
---
- name: reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: restart open-iscsi
ansible.builtin.systemd:
name: open-iscsi
state: restarted
enabled: true

View File

@ -1,69 +0,0 @@
---
- name: Require iscsi_portal when logging out absent targets
ansible.builtin.assert:
that:
- iscsi_portal is defined
- (iscsi_portal | string | length) > 0
fail_msg: "iscsi_absent_targets is set but iscsi_portal is missing/empty."
when: (iscsi_absent_targets | default([]) | length) > 0
- name: Unmount absent iSCSI mountpoints
ansible.builtin.mount:
path: "{{ item.path | default(item) }}"
state: unmounted
loop: "{{ iscsi_absent_mounts | default([]) }}"
loop_control:
label: "{{ item.path | default(item) }}"
tags:
- iscsi_cleanup
- name: Remove absent iSCSI fstab entries
ansible.builtin.lineinfile:
path: /etc/fstab
state: absent
regexp: "^\\s*\\S+\\s+{{ (item.path | default(item)) | regex_escape }}\\s+"
loop: "{{ iscsi_absent_mounts | default([]) }}"
loop_control:
label: "{{ item.path | default(item) }}"
tags:
- iscsi_cleanup
- name: Check for active sessions for absent targets
ansible.builtin.shell: >-
iscsiadm -m session 2>/dev/null | grep -Fq -- {{ item | quote }}
register: _iscsi_absent_target_session_checks
changed_when: false
failed_when: false
loop: "{{ iscsi_absent_targets | default([]) }}"
loop_control:
label: "{{ item }}"
when: (iscsi_absent_targets | default([]) | length) > 0
tags:
- iscsi_cleanup
- name: Logout absent iSCSI targets (active sessions only)
ansible.builtin.command: >-
iscsiadm -m node -T {{ item.item }} -p {{ iscsi_portal }} --logout
register: _iscsi_absent_target_logout
changed_when: false
failed_when: false
loop: "{{ _iscsi_absent_target_session_checks.results | default([]) }}"
loop_control:
label: "{{ item.item }}"
when:
- (iscsi_absent_targets | default([]) | length) > 0
- item.rc == 0
tags:
- iscsi_cleanup
- name: Delete node records for absent iSCSI targets (best-effort)
ansible.builtin.command: >-
iscsiadm -m node -o delete -T {{ item }} -p {{ iscsi_portal }}
changed_when: false
failed_when: false
loop: "{{ iscsi_absent_targets | default([]) }}"
loop_control:
label: "{{ item }}"
when: (iscsi_absent_targets | default([]) | length) > 0
tags:
- iscsi_cleanup

View File

@ -1,338 +0,0 @@
---
- name: Ensure mountpoint exists
ansible.builtin.file:
path: "{{ m.path }}"
state: directory
mode: "0755"
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Reset computed mount source
ansible.builtin.set_fact:
_iscsi_mount_src: ""
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Decide whether a block device is required for this mount
ansible.builtin.set_fact:
_iscsi_need_blockdev: >-
{{ (m.mkfs_once | default(false) | bool)
or (m.mkfs_if_missing | default(false) | bool)
or (m.device is defined and (m.device | string | length) > 0)
or (m.src is not defined) or ((m.src | default('') | string | length) == 0) }}
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Parse iSCSI portal (host/port)
ansible.builtin.set_fact:
_iscsi_portal_host: "{{ (iscsi_portal | string).split(':')[0] }}"
_iscsi_portal_port: "{{ ((iscsi_portal | string).split(':') | length > 1) | ternary((iscsi_portal | string).split(':')[1], '3260') }}"
when:
- _iscsi_need_blockdev | default(false) | bool
- iscsi_portal is defined
- (iscsi_portal | string | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Use explicitly provided block device (if any)
ansible.builtin.set_fact:
_iscsi_blockdev: "{{ m.device }}"
when:
- _iscsi_need_blockdev | default(false) | bool
- m.device is defined
- (m.device | string | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Compute iSCSI by-path pattern
ansible.builtin.set_fact:
_iscsi_by_path_pattern: >-
ip-{{ _iscsi_portal_host }}:{{ _iscsi_portal_port }}-iscsi-{{ iscsi_target.iqn }}-lun-{{ (m.lun is defined) | ternary((m.lun | string), '*') }}
when:
- _iscsi_need_blockdev | default(false) | bool
- (_iscsi_blockdev is not defined) or ((_iscsi_blockdev | string | length) == 0)
- iscsi_target is defined
- iscsi_target.iqn is defined
- _iscsi_portal_host is defined
- (_iscsi_portal_host | string | length) > 0
- _iscsi_portal_port is defined
- (_iscsi_portal_port | string | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Wait for iSCSI by-path device to appear
ansible.builtin.find:
paths:
- /dev/disk/by-path
patterns:
- "{{ _iscsi_by_path_pattern }}"
file_type: any
register: _iscsi_by_path_find
until: (_iscsi_by_path_find.matched | default(0) | int) > 0
retries: "{{ ((m.device_timeout | default(60)) | int // 5) + 1 }}"
delay: 5
when:
- _iscsi_need_blockdev | default(false) | bool
- (_iscsi_blockdev is not defined) or ((_iscsi_blockdev | string | length) == 0)
- _iscsi_by_path_pattern is defined
- (_iscsi_by_path_pattern | string | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Select iSCSI block device from by-path matches
ansible.builtin.set_fact:
_iscsi_blockdev: "{{ (_iscsi_by_path_find.files | map(attribute='path') | list | sort | first) }}"
when:
- _iscsi_need_blockdev | default(false) | bool
- (_iscsi_blockdev is not defined) or ((_iscsi_blockdev | string | length) == 0)
- _iscsi_by_path_find is defined
- (_iscsi_by_path_find.matched | default(0) | int) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Read existing filesystem type (if any)
ansible.builtin.command: "blkid -o value -s TYPE {{ _iscsi_blockdev }}"
register: _iscsi_blkid_type
changed_when: false
failed_when: false
check_mode: no
when:
- _iscsi_need_blockdev | default(false) | bool
- _iscsi_blockdev is defined
- (_iscsi_blockdev | string | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- 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
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- name: Compute mkfs marker path (mkfs_once)
ansible.builtin.set_fact:
_iscsi_mkfs_marker: >-
/var/lib/prole/iscsi/mkfs-
{{ (((iscsi_target.iqn | default('unknown-iqn')) ~ '-' ~ (m.path | default('unknown-path')))
| regex_replace('[^A-Za-z0-9_.-]', '_')) }}
.done
when: m.mkfs_once | default(false) | bool
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- 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
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- 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
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- name: Refuse to mkfs when mountpoint is mounted
ansible.builtin.fail:
msg: "Refusing to mkfs for {{ m.path }} because it 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
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- name: Require iSCSI block device path for mkfs_once
ansible.builtin.assert:
that:
- _iscsi_blockdev is defined
- (_iscsi_blockdev | string | length) > 0
fail_msg: "mkfs_once requested for {{ m.path }} but no block device was computed (set m.device or ensure iscsi_target+iscsi_portal are available)."
when:
- m.mkfs_once | default(false) | bool
- _iscsi_mkfs_marker_stat.stat.exists is not defined or not _iscsi_mkfs_marker_stat.stat.exists
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- name: Re-initialize filesystem (mkfs_once)
ansible.builtin.command: >-
{{ ((m.fstype | default('ext4')) == 'xfs')
| ternary(
'mkfs.xfs -f ' ~ _iscsi_blockdev,
(((m.fstype | default('ext4')) == 'ext4')
| ternary(
'mkfs.ext4 -F ' ~ _iscsi_blockdev,
'mkfs -t ' ~ (m.fstype | default('ext4')) ~ ' ' ~ _iscsi_blockdev
)
)
) }}
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
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- name: Create filesystem when missing (mkfs_if_missing)
ansible.builtin.command: >-
{{ ((m.fstype | default('ext4')) == 'xfs')
| ternary(
'mkfs.xfs -f ' ~ _iscsi_blockdev,
(((m.fstype | default('ext4')) == 'ext4')
| ternary(
'mkfs.ext4 -F ' ~ _iscsi_blockdev,
'mkfs -t ' ~ (m.fstype | default('ext4')) ~ ' ' ~ _iscsi_blockdev
)
)
) }}
changed_when: true
when:
- m.mkfs_if_missing | default(false) | bool
- _iscsi_blockdev is defined
- (_iscsi_blockdev | string | length) > 0
- _iscsi_blkid_type is not defined or (_iscsi_blkid_type.stdout | default('') | trim) == ''
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- name: Refuse to mount when existing filesystem type does not match
ansible.builtin.fail:
msg: >-
Existing filesystem type on {{ _iscsi_blockdev }} is
'{{ _iscsi_blkid_type.stdout | default('') | trim }}' but mount {{ m.path }}
requests fstype='{{ m.fstype | default('ext4') }}'.
Wipe the device signature (e.g. wipefs) or set mkfs_once=true if you intend
to reformat this LUN.
when:
- _iscsi_blkid_type is defined
- (_iscsi_blkid_type.stdout | default('') | trim) != ''
- (_iscsi_blkid_type.stdout | trim) != (m.fstype | default('ext4'))
- not (m.mkfs_once | default(false) | bool)
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- 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
tags:
- iscsi
- iscsi_mkfs
- iscsi_storage
- name: Resolve mount source
ansible.builtin.set_fact:
_iscsi_mount_src: "{{ m.src }}"
when:
- m.src is defined
- (m.src | string | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Resolve mount source UUID from block device
ansible.builtin.command: "blkid -o value -s UUID {{ _iscsi_blockdev }}"
register: _iscsi_blkid_uuid
changed_when: false
check_mode: no
when:
- (_iscsi_mount_src | default('') | string | length) == 0
- _iscsi_blockdev is defined
- (_iscsi_blockdev | string | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Require resolved UUID for mounting
ansible.builtin.assert:
that:
- _iscsi_blkid_uuid is defined
- (_iscsi_blkid_uuid.stdout | default('') | trim) != ''
fail_msg: "Unable to resolve a filesystem UUID for {{ m.path }} from {{ _iscsi_blockdev }}."
when:
- (_iscsi_mount_src | default('') | string | length) == 0
- not (ansible_check_mode and ((m.mkfs_once | default(false) | bool) or (m.mkfs_if_missing | default(false) | bool)))
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Set mount source to UUID
ansible.builtin.set_fact:
_iscsi_mount_src: "UUID={{ _iscsi_blkid_uuid.stdout | trim }}"
when:
- (_iscsi_mount_src | default('') | string | length) == 0
- _iscsi_blkid_uuid is defined
- (_iscsi_blkid_uuid.stdout | default('') | trim) != ''
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Mount {{ m.path }}
ansible.builtin.mount:
path: "{{ m.path }}"
src: "{{ _iscsi_mount_src }}"
fstype: "{{ m.fstype | default('ext4') }}"
opts: "{{ m.opts | default('_netdev,noatime') }}"
state: mounted
when: (_iscsi_mount_src | default('') | string | trim | length) > 0
tags:
- iscsi
- iscsi_mount
- iscsi_storage

View File

@ -1,74 +0,0 @@
---
- name: Ensure node record exists for target
ansible.builtin.command: >
iscsiadm -m node -T {{ t.iqn }} -p {{ iscsi_portal }}
register: node_check
changed_when: false
failed_when: false
- name: Create node record if missing
ansible.builtin.command: >
iscsiadm -m node -o new -T {{ t.iqn }} -p {{ iscsi_portal }}
when: node_check.rc != 0
- name: Configure CHAP authmethod
ansible.builtin.command: >
iscsiadm -m node -T {{ t.iqn }} -p {{ iscsi_portal }}
--op update -n node.session.auth.authmethod -v CHAP
when: (t.chap_user | default('')) | length > 0
- name: Configure CHAP username
ansible.builtin.command: >
iscsiadm -m node -T {{ t.iqn }} -p {{ iscsi_portal }}
--op update -n node.session.auth.username -v {{ t.chap_user }}
when: (t.chap_user | default('')) | length > 0
- name: Configure CHAP password
ansible.builtin.command: >
iscsiadm -m node -T {{ t.iqn }} -p {{ iscsi_portal }}
--op update -n node.session.auth.password -v {{ t.chap_password }}
when: (t.chap_user | default('')) | length > 0
no_log: true
- name: Check if target session is already logged in
ansible.builtin.shell: >-
iscsiadm -m session 2>/dev/null | grep -Fq -- {{ t.iqn | quote }}
register: session_check
changed_when: false
failed_when: false
- name: Login to target
ansible.builtin.command: >
iscsiadm -m node -T {{ t.iqn }} -p {{ iscsi_portal }} --login
register: login
changed_when: false
failed_when: false
when: session_check.rc != 0
- name: Re-check session after login attempt
ansible.builtin.shell: >-
iscsiadm -m session 2>/dev/null | grep -Fq -- {{ t.iqn | quote }}
register: session_after_login
changed_when: false
failed_when: false
when: session_check.rc != 0
- name: Fail if login did not establish a session
ansible.builtin.fail:
msg: |-
iSCSI login failed for {{ t.iqn }} at {{ iscsi_portal }}.
login.rc={{ login.rc | default('') }}
login.stdout={{ (login.stdout | default('')) | trim }}
login.stderr={{ (login.stderr | default('')) | trim }}
when:
- session_check.rc != 0
- session_after_login is defined
- session_after_login.rc != 0
- login is defined
- login.rc not in [0, 15]
- name: Ensure iSCSI autologin
ansible.builtin.command: >
iscsiadm -m node -T {{ t.iqn }} -p {{ iscsi_portal }}
--op update -n node.startup -v automatic

View File

@ -1,9 +0,0 @@
---
- name: Mount target filesystems
ansible.builtin.include_tasks: iscsi_mount.yml
loop: "{{ t.mounts | default([]) }}"
loop_control:
loop_var: m
label: "{{ m.path | default('<unknown-mountpoint>') }}"
vars:
iscsi_target: "{{ t }}"

View File

@ -1,160 +0,0 @@
---
- name: Install iSCSI dependencies
ansible.builtin.package:
name:
- open-iscsi
- util-linux
- xfsprogs
state: present
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Ensure iscsid enabled
ansible.builtin.service:
name: iscsid
state: started
enabled: true
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Check if k3s systemd unit exists
ansible.builtin.stat:
path: /etc/systemd/system/k3s.service
register: _k3s_unit
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Check if k3s-agent systemd unit exists
ansible.builtin.stat:
path: /etc/systemd/system/k3s-agent.service
register: _k3s_agent_unit
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Ensure k3s stops/restarts before open-iscsi (systemd drop-in)
block:
- name: Ensure k3s.service.d exists
ansible.builtin.file:
path: /etc/systemd/system/k3s.service.d
state: directory
mode: "0755"
- name: Install k3s open-iscsi ordering drop-in
ansible.builtin.copy:
dest: /etc/systemd/system/k3s.service.d/open-iscsi.conf
mode: "0644"
content: |
[Unit]
Wants=open-iscsi.service
After=open-iscsi.service
PartOf=open-iscsi.service
notify:
- reload systemd
when: _k3s_unit.stat.exists | default(false)
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Ensure k3s-agent stops/restarts before open-iscsi (systemd drop-in)
block:
- name: Ensure k3s-agent.service.d exists
ansible.builtin.file:
path: /etc/systemd/system/k3s-agent.service.d
state: directory
mode: "0755"
- name: Install k3s-agent open-iscsi ordering drop-in
ansible.builtin.copy:
dest: /etc/systemd/system/k3s-agent.service.d/open-iscsi.conf
mode: "0644"
content: |
[Unit]
Wants=open-iscsi.service
After=open-iscsi.service
PartOf=open-iscsi.service
notify:
- reload systemd
when: _k3s_agent_unit.stat.exists | default(false)
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Ensure open-iscsi systemd override directory exists
ansible.builtin.file:
path: /etc/systemd/system/open-iscsi.service.d
state: directory
mode: "0755"
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Install open-iscsi retry-on-boot override
ansible.builtin.copy:
dest: /etc/systemd/system/open-iscsi.service.d/retry-on-boot.conf
mode: "0644"
content: |
[Unit]
Wants=network-online.target iscsid.service
After=network-online.target iscsid.service
[Service]
Restart=on-failure
RestartSec=10s
StartLimitIntervalSec=120
StartLimitBurst=10
notify:
- reload systemd
- restart open-iscsi
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Apply open-iscsi override immediately (if changed)
ansible.builtin.meta: flush_handlers
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Ensure open-iscsi enabled
ansible.builtin.systemd:
name: open-iscsi
enabled: true
tags:
- iscsi
- iscsi_login
- iscsi_storage
- name: Discover iSCSI targets
ansible.builtin.command: >-
iscsiadm -m discovery -t st -p {{ iscsi_portal }}
register: _iscsi_discovery
changed_when: false
tags:
- iscsi
- iscsi_discovery
- iscsi_login
- iscsi_storage
- name: Configure and login iSCSI targets
ansible.builtin.include_tasks: iscsi_target.yml
loop: "{{ iscsi_targets | default([]) }}"
loop_control:
loop_var: t
label: "{{ t.iqn | default('<unknown-iqn>') }}"
tags:
- iscsi
- iscsi_login
- iscsi_storage

View File

@ -1,6 +0,0 @@
---
- name: iSCSI auth/login (storage)
ansible.builtin.include_tasks: login.yml
- name: iSCSI mounts (filesystem)
ansible.builtin.include_tasks: mount.yml

View File

@ -1,97 +0,0 @@
---
- name: Compute legacy mountpoints to clean up (/prole/d00x -> /synology/d00x)
ansible.builtin.set_fact:
_iscsi_legacy_absent_mounts: >-
{{
(iscsi_targets | default([])
| map(attribute='mounts') | list | flatten
| selectattr('path', 'defined')
| map(attribute='path') | list
| select('match', '^/synology/d[0-9]{3}$')
| map('regex_replace', '^/synology/', '/prole/')
| list)
}}
changed_when: false
tags:
- iscsi_cleanup
- name: Unmount legacy iSCSI mounts (old /prole/d00x layout)
ansible.builtin.mount:
path: "{{ item }}"
state: unmounted
loop: "{{ _iscsi_legacy_absent_mounts | default([]) }}"
loop_control:
label: "{{ item }}"
tags:
- iscsi_cleanup
- name: Remove legacy iSCSI fstab entries (old /prole/d00x layout)
ansible.builtin.lineinfile:
path: /etc/fstab
state: absent
regexp: "^\\s*\\S+\\s+{{ item | regex_escape }}\\s+"
loop: "{{ _iscsi_legacy_absent_mounts | default([]) }}"
loop_control:
label: "{{ item }}"
tags:
- iscsi_cleanup
- name: Unmount stale iSCSI mounts
ansible.builtin.mount:
path: "{{ item.path | default(item) }}"
state: unmounted
loop: "{{ iscsi_absent_mounts | default([]) }}"
loop_control:
label: "{{ item.path | default(item) }}"
tags:
- iscsi_cleanup
- name: Guardrail - iSCSI must not manage /var/lib/rancher
ansible.builtin.assert:
that:
- >-
(iscsi_targets | default([])
| map(attribute='mounts') | list | flatten
| selectattr('path', 'equalto', '/var/lib/rancher')
| list | length) == 0
fail_msg: >-
Inventory attempts to mount `/var/lib/rancher` via iSCSI. K3s state storage is
intended to be local host storage (e.g. USB3 SSD on myrddin). Remove
`/var/lib/rancher` from `iscsi_targets[*].mounts` (or set
`iscsi_allow_rancher_mount: true` if you are intentionally overriding this).
when: not (iscsi_allow_rancher_mount | default(false) | bool)
- name: Remove stale iSCSI fstab entries
ansible.builtin.lineinfile:
path: /etc/fstab
state: absent
regexp: "^\\s*\\S+\\s+{{ (item.path | default(item)) | regex_escape }}\\s+"
loop: "{{ iscsi_absent_mounts | default([]) }}"
loop_control:
label: "{{ item.path | default(item) }}"
tags:
- iscsi_cleanup
- name: Mount filesystems for iSCSI targets
ansible.builtin.include_tasks: iscsi_target_mounts.yml
loop: "{{ iscsi_targets | default([]) }}"
loop_control:
loop_var: t
label: "{{ t.iqn | default('<unknown-iqn>') }}"
tags:
- iscsi
- iscsi_mount
- iscsi_storage
- name: Guardrail - ensure required mountpoints are mounted
ansible.builtin.command: "findmnt -n {{ item.path }}"
register: _findmnt
changed_when: false
failed_when: _findmnt.rc != 0
loop: "{{ iscsi_targets | default([]) | map(attribute='mounts') | list | flatten }}"
loop_control:
label: "{{ item.path }}"
tags:
- iscsi
- iscsi_mount
- iscsi_storage

View File

@ -1,113 +0,0 @@
---
k3s_enabled: true
k3s_state: present
k3s_version: ""
k3s_guard_token_drift: true
# k3s reset workflow
# By default, cleanup/uninstall is local-only. The `k3s_reset` playbook enables
# cluster-side node removal explicitly.
k3s_reset_force_cluster_node_delete: false
k3s_reset_kubeconfig_path: /etc/rancher/k3s/k3s.yaml
# k3s service startup can be slow on first boot (image pulls, containerd unpacking, etc.).
# The service task is best-effort and then Ansible polls `systemctl is-active` using these knobs.
k3s_service_start_retries: 60
k3s_service_start_delay: 5
# Destructive repair/refresh workflow (server only)
# A refresh wipes the k3s installation/state to a blank slate but preserves the
# join token and CA material so existing agents can rejoin without changing the token.
k3s_refresh_backup_root: /root/k3s-refresh-backups
k3s_refresh_confirm: ""
k3s_refresh_run_configure: true
# CloudNative-PG
# Pin operator/plugin version by default to avoid unanticipated upgrades and load spikes.
# Upgrades must be explicitly enabled via `k3s_cnpg_upgrade: true`.
k3s_cnpg_version_default: "1.28.1"
k3s_cnpg_upgrade: false
k3s_role: agent
k3s_cluster_init: false
k3s_server_url: ""
k3s_token: "{{ vault_k3s_token | default('') }}"
# Optional: if `k3s_token` is a full node-token (`K10...::server:...`), set this
# to the raw secret (`...` after `::server:`) for the server config.
k3s_server_token: ""
k3s_disable: []
k3s_tls_sans: []
k3s_service_node_labels: []
k3s_node_taints: []
k3s_disable_agent: false
# K3s state storage
# `/var/lib/rancher` is treated as local host storage (not iSCSI/shared).
k3s_rancher_mountpoint: /var/lib/rancher
k3s_rancher_storage_class: local
k3s_data_dir: "{{ k3s_rancher_mountpoint }}/k3s"
# Guardrail: when enabled, k3s must not run unless `k3s_rancher_mountpoint` is a
# dedicated mount (i.e. not backed by the same device as `/`).
#
# This is intentionally opt-in because some nodes may legitimately run k3s with
# `/var/lib/rancher` on the root filesystem.
k3s_rancher_mount_required: false
# If `k3s_rancher_mount_required` is true, these must be set to manage the mount
# via Ansible (/etc/fstab + mount).
k3s_rancher_mount_src: ""
k3s_rancher_mount_fstype: ""
k3s_rancher_mount_opts: "noatime"
k3s_rancher_mount_passno: 2
k3s_write_kubeconfig_mode: "0640"
k3s_kubeconfig_group: kubeadm
k3s_kubeconfig_users: []
k3s_registry_config_enabled: true
k3s_registry_config_path: /etc/rancher/k3s/registries.yaml
k3s_registry_host: ""
k3s_registry_namespace: ""
k3s_registry_port: 5000
# Internal Prole registry is HTTP by default; set to "https" when TLS is configured.
k3s_registry_endpoint_scheme: "http"
# Pulling large images on every run can cause major load spikes; keep this opt-in.
k3s_prestage_images: false
k3s_prestage_images_platform: "linux/arm64"
k3s_prestage_images_list:
- docker.io/library/registry:2
- docker.io/prom/prometheus:v2.55.1
- docker.io/prom/alertmanager:v0.27.0
- quay.io/prometheus/node-exporter:v1.8.2
- quay.io/prometheus-operator/prometheus-operator:v0.71.2
- ghcr.io/openbao/openbao:2.0.0
- ghcr.io/opentofu/opentofu:1.8.2
- docker.io/library/kong:3.8
k3s_cert_manager_version: "v1.19.4"
k3s_service_hostname: "{{ SERVICE_HOSTNAME | default('svc.prole.org') }}"
k3s_service_cert_name: "{{ k3s_service_hostname | replace('.', '-') }}"
k3s_service_tls_secret_name: "{{ k3s_service_cert_name }}-tls"
k3s_service_cluster_issuer: "letsencrypt-prod"
# Host firewall: keep svc.prole.org reachable even when Tailscale inserts
# restrictive filter rules (e.g., `ts-input`/REJECT) near the top of `INPUT`.
# This role installs a small systemd oneshot that re-applies the rules at boot.
k3s_firewall_svc_allow_enabled: true
k3s_firewall_svc_allow_chain: "PROLE-SVC-ALLOW"
k3s_firewall_svc_allow_tcp_ports:
- 80
- 443
# Traefik NodePorts observed on k3s; keep configurable.
- 31936
- 32370
# Legacy alias (svc-check used to own svc.prole.org). Keep for backward compatibility.
k3s_svc_check_domain: "{{ k3s_service_hostname }}"
k3s_svc_check_namespace: "svc-check"
k3s_svc_check_kong_namespace: "${SERVICE_NAMESPACE}"
k3s_svc_check_kong_configmap_name: "prole-svc-kong-config"
k3s_kong_namespace: "${SERVICE_NAMESPACE}"
k3s_kong_deployment: "prole-svc-kong"

View File

@ -1,40 +0,0 @@
---
- name: Restart k3s
ansible.builtin.service:
name: "{{ k3s_service_name | default('k3s') }}"
state: restarted
- name: Upgrade CNPG operator
listen: Upgrade CNPG operator
ansible.builtin.command: kubectl apply --server-side -f {{ cnpg_manifest_url }}
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: cnpg_upgrade_apply
changed_when: "'created' in cnpg_upgrade_apply.stdout or 'configured' in cnpg_upgrade_apply.stdout"
- name: Verify CNPG controller rollout (upgrade)
listen: Upgrade CNPG operator
ansible.builtin.command: kubectl rollout status deployment -n cnpg-system cnpg-controller-manager --timeout=300s
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: cnpg_upgrade_rollout_check
changed_when: false
failed_when: false
- name: Restart CNPG controller if rollout failed (upgrade)
listen: Upgrade CNPG operator
ansible.builtin.command: kubectl rollout restart deployment -n cnpg-system cnpg-controller-manager
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
changed_when: true
when:
- cnpg_upgrade_rollout_check is defined
- cnpg_upgrade_rollout_check.rc != 0
- name: Final CNPG controller rollout check (upgrade)
listen: Upgrade CNPG operator
ansible.builtin.command: kubectl rollout status deployment -n cnpg-system cnpg-controller-manager --timeout=300s
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
changed_when: false
failed_when: false

View File

@ -1,119 +0,0 @@
---
- name: Ensure cert-manager CRDs exist for ACME
ansible.builtin.command: k3s kubectl get crd certificates.cert-manager.io
register: k3s_acme_crd
changed_when: false
failed_when: k3s_acme_crd.rc != 0
run_once: true
- name: Check for flannel DaemonSet (k3s default CNI)
ansible.builtin.command: k3s kubectl -n kube-system get daemonset kube-flannel-ds
register: _k3s_flannel_ds
changed_when: false
failed_when: false
run_once: true
- name: Wait for flannel DaemonSet rollout (ensures pod network is routable)
ansible.builtin.command: k3s kubectl -n kube-system rollout status daemonset/kube-flannel-ds --timeout=300s
register: _k3s_flannel_rollout
changed_when: false
retries: 6
delay: 10
until: _k3s_flannel_rollout.rc == 0
when: _k3s_flannel_ds.rc == 0
run_once: true
- name: Check for kube-proxy DaemonSet
ansible.builtin.command: k3s kubectl -n kube-system get daemonset kube-proxy
register: _k3s_kube_proxy_ds
changed_when: false
failed_when: false
run_once: true
- name: Wait for kube-proxy DaemonSet rollout
ansible.builtin.command: k3s kubectl -n kube-system rollout status daemonset/kube-proxy --timeout=300s
register: _k3s_kube_proxy_rollout
changed_when: false
retries: 6
delay: 10
until: _k3s_kube_proxy_rollout.rc == 0
when: _k3s_kube_proxy_ds.rc == 0
run_once: true
- name: Wait for cert-manager-webhook deployment (required for validating ClusterIssuer/Certificate)
ansible.builtin.command: k3s kubectl -n cert-manager rollout status deployment/cert-manager-webhook --timeout=900s
register: k3s_certmgr_webhook_rollout
changed_when: false
retries: 3
delay: 20
until: k3s_certmgr_webhook_rollout.rc == 0
run_once: true
- name: Wait for cert-manager-webhook service endpoints
ansible.builtin.command: >-
k3s kubectl -n cert-manager get endpoints cert-manager-webhook
-o jsonpath='{.subsets[*].addresses[*].ip}'
register: k3s_certmgr_webhook_endpoints
changed_when: false
retries: 90
delay: 10
until:
- k3s_certmgr_webhook_endpoints.rc == 0
- (k3s_certmgr_webhook_endpoints.stdout | trim) | length > 0
run_once: true
- name: Set ACME contact email (defaults to admin@prole.org)
ansible.builtin.set_fact:
svc_check_acme_email: "{{ letsencrypt_email | default('admin@prole.org') }}"
run_once: true
- name: Ensure namespace exists for ACME certificate secret
ansible.builtin.shell: |
k3s kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: {{ k3s_svc_check_kong_namespace }}
EOF
args:
executable: /bin/bash
run_once: true
- name: Configure ACME ClusterIssuer and Certificate for service hostname
ansible.builtin.shell: |
k3s kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: {{ k3s_service_cluster_issuer }}
spec:
acme:
email: {{ svc_check_acme_email }}
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: {{ k3s_service_cluster_issuer }}
solvers:
- http01:
ingress:
class: traefik
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ k3s_service_cert_name }}
namespace: {{ k3s_svc_check_kong_namespace }}
spec:
secretName: {{ k3s_service_tls_secret_name }}
issuerRef:
name: {{ k3s_service_cluster_issuer }}
kind: ClusterIssuer
dnsNames:
- {{ k3s_service_hostname }}
EOF
args:
executable: /bin/bash
register: k3s_acme_apply
retries: 90
delay: 10
until: k3s_acme_apply.rc == 0
run_once: true

View File

@ -1,53 +0,0 @@
---
- name: Check for cert-manager CRD
ansible.builtin.command: k3s kubectl get crd certificates.cert-manager.io
register: k3s_certmgr_crd
changed_when: false
failed_when: false
run_once: true
- name: Apply cert-manager (CRDs + controller)
ansible.builtin.command: >-
k3s kubectl apply --validate=false -f
https://github.com/cert-manager/cert-manager/releases/download/{{ k3s_cert_manager_version }}/cert-manager.yaml
register: _k3s_certmgr_apply
changed_when: >-
(_k3s_certmgr_apply.stdout | default('') | lower) is search('configured')
or (_k3s_certmgr_apply.stdout | default('') | lower) is search('created')
retries: 30
delay: 10
until: _k3s_certmgr_apply.rc == 0
run_once: true
- name: Wait for cert-manager deployment to have a ready replica
ansible.builtin.command: >-
k3s kubectl -n cert-manager get deployment cert-manager
-o jsonpath='{.status.readyReplicas}'
register: _k3s_certmgr_ready
changed_when: false
retries: 90
delay: 10
until: (_k3s_certmgr_ready.stdout | default('0') | int) >= 1
run_once: true
- name: Wait for cert-manager-cainjector deployment to have a ready replica
ansible.builtin.command: >-
k3s kubectl -n cert-manager get deployment cert-manager-cainjector
-o jsonpath='{.status.readyReplicas}'
register: _k3s_certmgr_cainjector_ready
changed_when: false
retries: 90
delay: 10
until: (_k3s_certmgr_cainjector_ready.stdout | default('0') | int) >= 1
run_once: true
- name: Wait for cert-manager-webhook deployment to have a ready replica
ansible.builtin.command: >-
k3s kubectl -n cert-manager get deployment cert-manager-webhook
-o jsonpath='{.status.readyReplicas}'
register: _k3s_certmgr_webhook_ready
changed_when: false
retries: 90
delay: 10
until: (_k3s_certmgr_webhook_ready.stdout | default('0') | int) >= 1
run_once: true

View File

@ -1,314 +0,0 @@
---
- name: Set effective k3s data-dir
ansible.builtin.set_fact:
k3s_effective_data_dir: "{{ k3s_data_dir | default('/var/lib/rancher/k3s', true) }}"
# k3s reset: cluster-side node removal (best-effort)
#
# Why this exists:
# - Prevents stale `NotReady` node objects after a node reset.
# - Prevents DaemonSet pods stuck `Terminating` (the scheduler still thinks the node exists).
# - Prevents lingering `kube-node-lease` entries which can cause subsequent install/join hangs.
#
# Important safety rules:
# - We run these `kubectl` operations on a healthy control-plane host via `delegate_to`.
# - We skip if there is no reachable control-plane host or if its kubeconfig is missing.
# - We do not attempt this when resetting the *last* control-plane node (there is no other
# healthy server to delegate to, and we don't want the reset to break the cluster).
- name: Build node-name candidates for cluster-side cleanup (prefer inventory_hostname)
ansible.builtin.set_fact:
k3s_reset_node_name_candidates: >-
{{ [inventory_hostname,
ansible_fqdn | default(''),
ansible_hostname | default('')]
| reject('equalto', '')
| unique
| list }}
k3s_reset_node_name: >-
{{ ([inventory_hostname,
ansible_fqdn | default(''),
ansible_hostname | default('')]
| reject('equalto', '')
| unique
| list)[0] }}
when: k3s_reset_force_cluster_node_delete | bool
- name: Pick control-plane delegate host for cluster-side cleanup (first available k3s server)
ansible.builtin.set_fact:
k3s_reset_control_plane_hosts: "{{ groups['k3s_servers'] | default([]) }}"
k3s_reset_is_last_control_plane: >-
{{ (inventory_hostname in (groups['k3s_servers'] | default([])))
and ((groups['k3s_servers'] | default([])) | length == 1) }}
k3s_reset_delegate_host: >-
{{ ((groups['k3s_servers'] | default([]))
| difference([inventory_hostname])
| first) | default('') }}
when: k3s_reset_force_cluster_node_delete | bool
- name: Log why cluster-side node removal is skipped (last control-plane)
ansible.builtin.debug:
msg: >-
Skipping cluster-side node deletion for {{ inventory_hostname }} because it is the last
control-plane host in inventory (no other server to delegate kubectl to).
when:
- k3s_reset_force_cluster_node_delete | bool
- k3s_reset_is_last_control_plane | bool
- name: Log why cluster-side node removal is skipped (no control-plane delegate host)
ansible.builtin.debug:
msg: >-
Skipping cluster-side node deletion for {{ inventory_hostname }} because no control-plane
host is available to delegate to (inventory group `k3s_servers` is empty, or only contains
this host).
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (k3s_reset_delegate_host | default('') | length) == 0
- name: Check whether delegated control-plane host is reachable
ansible.builtin.ping:
delegate_to: "{{ (k3s_reset_delegate_host | default('') | trim | length > 0) | ternary(k3s_reset_delegate_host, omit) }}"
register: _k3s_reset_delegate_ping
failed_when: false
changed_when: false
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (k3s_reset_delegate_host | default('') | length) > 0
- name: Log why cluster-side node removal is skipped (control-plane host unreachable)
ansible.builtin.debug:
msg: >-
Skipping cluster-side node deletion for {{ inventory_hostname }} because delegated
control-plane host '{{ k3s_reset_delegate_host }}' is unreachable.
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (k3s_reset_delegate_host | default('') | length) > 0
- _k3s_reset_delegate_ping is not defined or (_k3s_reset_delegate_ping.ping | default('')) != 'pong'
- name: Check for kubeconfig on delegated control-plane host
ansible.builtin.stat:
path: "{{ k3s_reset_kubeconfig_path }}"
delegate_to: "{{ (k3s_reset_delegate_host | default('') | trim | length > 0) | ternary(k3s_reset_delegate_host, omit) }}"
register: _k3s_reset_kubeconfig_stat
failed_when: false
changed_when: false
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- name: Log why cluster-side node removal is skipped (kubeconfig missing on delegate)
ansible.builtin.debug:
msg: >-
Skipping cluster-side node deletion for {{ inventory_hostname }} because kubeconfig
'{{ k3s_reset_kubeconfig_path }}' is missing on delegated control-plane host
'{{ k3s_reset_delegate_host }}'.
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- _k3s_reset_kubeconfig_stat is defined
- not (_k3s_reset_kubeconfig_stat.stat.exists | default(false) | bool)
- name: Fetch cluster node list (best-effort) to resolve the exact Kubernetes node name
ansible.builtin.command: >-
timeout 15s kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'
--request-timeout=5s
delegate_to: "{{ (k3s_reset_delegate_host | default('') | trim | length > 0) | ternary(k3s_reset_delegate_host, omit) }}"
environment:
KUBECONFIG: "{{ k3s_reset_kubeconfig_path }}"
register: _k3s_reset_nodes_list
failed_when: false
changed_when: false
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- (_k3s_reset_kubeconfig_stat.stat.exists | default(false)) | bool
- name: Resolve Kubernetes node name from cluster (fallback to inventory_hostname)
ansible.builtin.set_fact:
k3s_reset_node_name: >-
{{ (k3s_reset_node_name_candidates
| select('in', _k3s_reset_nodes_list.stdout_lines | default([]))
| list
| first)
| default(k3s_reset_node_name) }}
when:
- k3s_reset_force_cluster_node_delete | bool
- _k3s_reset_nodes_list is defined
- name: Log planned cluster-side cleanup target
ansible.builtin.debug:
msg: >-
Cluster-side cleanup enabled. Will attempt to delete node/lease '{{ k3s_reset_node_name }}'
via delegated control-plane host '{{ k3s_reset_delegate_host }}' (kubeconfig: {{ k3s_reset_kubeconfig_path }}).
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- (_k3s_reset_kubeconfig_stat.stat.exists | default(false)) | bool
- name: Cluster-side cleanup - delete node object (best-effort)
ansible.builtin.command: >-
timeout 15s kubectl delete node {{ k3s_reset_node_name }} --ignore-not-found=true
--wait=false --request-timeout=5s
delegate_to: "{{ (k3s_reset_delegate_host | default('') | trim | length > 0) | ternary(k3s_reset_delegate_host, omit) }}"
environment:
KUBECONFIG: "{{ k3s_reset_kubeconfig_path }}"
register: _k3s_reset_delete_node_1
failed_when: false
changed_when: "'deleted' in (_k3s_reset_delete_node_1.stdout | default('') | lower)"
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- (_k3s_reset_kubeconfig_stat.stat.exists | default(false)) | bool
- name: Cluster-side cleanup - patch away node finalizers if deletion is stuck (best-effort)
ansible.builtin.command: >-
timeout 15s kubectl patch node {{ k3s_reset_node_name }} --type=merge
-p '{"metadata":{"finalizers":[]}}' --request-timeout=5s
delegate_to: "{{ (k3s_reset_delegate_host | default('') | trim | length > 0) | ternary(k3s_reset_delegate_host, omit) }}"
environment:
KUBECONFIG: "{{ k3s_reset_kubeconfig_path }}"
register: _k3s_reset_patch_node_finalizers
failed_when: false
changed_when: "'patched' in (_k3s_reset_patch_node_finalizers.stdout | default('') | lower)"
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- (_k3s_reset_kubeconfig_stat.stat.exists | default(false)) | bool
- name: Cluster-side cleanup - delete node object again after patch (best-effort)
ansible.builtin.command: >-
timeout 15s kubectl delete node {{ k3s_reset_node_name }} --ignore-not-found=true
--wait=false --request-timeout=5s
delegate_to: "{{ (k3s_reset_delegate_host | default('') | trim | length > 0) | ternary(k3s_reset_delegate_host, omit) }}"
environment:
KUBECONFIG: "{{ k3s_reset_kubeconfig_path }}"
register: _k3s_reset_delete_node_2
failed_when: false
changed_when: "'deleted' in (_k3s_reset_delete_node_2.stdout | default('') | lower)"
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- (_k3s_reset_kubeconfig_stat.stat.exists | default(false)) | bool
- name: Cluster-side cleanup - delete kube-node-lease for the node (best-effort)
ansible.builtin.command: >-
timeout 15s kubectl delete lease {{ k3s_reset_node_name }} -n kube-node-lease --ignore-not-found=true
--wait=false --request-timeout=5s
delegate_to: "{{ (k3s_reset_delegate_host | default('') | trim | length > 0) | ternary(k3s_reset_delegate_host, omit) }}"
environment:
KUBECONFIG: "{{ k3s_reset_kubeconfig_path }}"
register: _k3s_reset_delete_lease
failed_when: false
changed_when: "'deleted' in (_k3s_reset_delete_lease.stdout | default('') | lower)"
when:
- k3s_reset_force_cluster_node_delete | bool
- not (k3s_reset_is_last_control_plane | default(false) | bool)
- (_k3s_reset_delegate_ping.ping | default('')) == 'pong'
- (_k3s_reset_kubeconfig_stat.stat.exists | default(false)) | bool
- name: Log cluster-side cleanup results (best-effort)
ansible.builtin.debug:
msg:
- "kubectl delete node (1): rc={{ _k3s_reset_delete_node_1.rc | default('n/a') }} stdout={{ _k3s_reset_delete_node_1.stdout | default('') | trim }} stderr={{ _k3s_reset_delete_node_1.stderr | default('') | trim }}"
- "kubectl patch node finalizers: rc={{ _k3s_reset_patch_node_finalizers.rc | default('n/a') }} stdout={{ _k3s_reset_patch_node_finalizers.stdout | default('') | trim }} stderr={{ _k3s_reset_patch_node_finalizers.stderr | default('') | trim }}"
- "kubectl delete node (2): rc={{ _k3s_reset_delete_node_2.rc | default('n/a') }} stdout={{ _k3s_reset_delete_node_2.stdout | default('') | trim }} stderr={{ _k3s_reset_delete_node_2.stderr | default('') | trim }}"
- "kubectl delete lease: rc={{ _k3s_reset_delete_lease.rc | default('n/a') }} stdout={{ _k3s_reset_delete_lease.stdout | default('') | trim }} stderr={{ _k3s_reset_delete_lease.stderr | default('') | trim }}"
when:
- k3s_reset_force_cluster_node_delete | bool
- _k3s_reset_delete_node_1 is defined or _k3s_reset_delete_lease is defined
- name: Best-effort purge kube-system workloads before uninstall
ansible.builtin.command: >-
timeout 15s kubectl -n kube-system delete all --all --ignore-not-found
--wait=false --request-timeout=5s
failed_when: false
changed_when: false
- name: Best-effort purge kube-system namespaces
ansible.builtin.command: >-
timeout 15s kubectl delete namespace kube-system --ignore-not-found
--wait=false --request-timeout=5s
failed_when: false
changed_when: false
- name: Force-remove kube-system finalizers if namespace is stuck
ansible.builtin.command: >-
timeout 15s kubectl patch namespace kube-system
-p '{"spec":{"finalizers":[]}}' --type=merge --request-timeout=5s
failed_when: false
changed_when: false
- name: Stop k3s services if present
ansible.builtin.service:
name: "{{ item }}"
state: stopped
enabled: false
loop:
- k3s
- k3s-agent
failed_when: false
- name: Check for k3s uninstall scripts
ansible.builtin.stat:
path: "{{ item }}"
loop:
- /usr/local/bin/k3s-uninstall.sh
- /usr/local/bin/k3s-agent-uninstall.sh
register: k3s_uninstall_scripts
- name: Run k3s uninstall scripts if present
ansible.builtin.command: "{{ item.stat.path }}"
loop: "{{ k3s_uninstall_scripts.results }}"
when: item.stat.exists
- name: Remove k3s systemd units and directories
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/rancher
- /etc/systemd/system/k3s.service
- /etc/systemd/system/k3s-agent.service
- /etc/systemd/system/k3s.service.d
- /etc/systemd/system/k3s-agent.service.d
- /etc/rancher/node
- /etc/rancher/k3s
- /etc/cni
- /opt/cni
- "{{ k3s_effective_data_dir }}"
- "{{ k3s_effective_data_dir }}/server/tls"
- "{{ k3s_effective_data_dir }}/server/cred"
- "{{ k3s_effective_data_dir }}/server/token"
- "{{ k3s_effective_data_dir }}/server/node-token"
- "{{ k3s_effective_data_dir }}/server/agent-token"
- "{{ k3s_effective_data_dir }}/server/db"
- "{{ k3s_effective_data_dir }}/agent"
- "{{ k3s_effective_data_dir }}/data"
- /var/lib/kubelet
- /var/lib/etcd
- /var/lib/cni
- /var/lib/containerd
- /run/k3s
- /run/flannel
- /var/log/k3s
- /usr/local/bin/k3s
- /usr/local/bin/k3s-killall.sh
- /usr/local/bin/k3s-uninstall.sh
- /usr/local/bin/k3s-agent-uninstall.sh
- /usr/local/bin/kubectl
- /usr/local/bin/crictl
- /usr/local/bin/ctr
- name: Reload systemd after k3s cleanup
ansible.builtin.systemd:
daemon_reload: true

View File

@ -1,213 +0,0 @@
---
- name: Check if controller has conf/k3s.cfg (optional)
ansible.builtin.stat:
path: "{{ role_path }}/../../../conf/k3s.cfg"
register: _k3s_prole_cfg_stat
delegate_to: localhost
- name: Load CNPG version from prole.cfg (pinned)
ansible.builtin.set_fact:
cnpg_version_raw: >-
{{ lookup(
'ansible.builtin.ini',
'CNPG_VERSION section=Global file=' ~ (role_path ~ '/../../../conf/k3s.cfg'),
default=(k3s_cnpg_version_default | default('1.28.1')),
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else (k3s_cnpg_version_default | default('1.28.1')) }}
- name: Normalize CNPG version
ansible.builtin.set_fact:
cnpg_version: "{{ cnpg_version_raw | trim | regex_replace('^v', '') }}"
- name: Derive CNPG release series from version
ansible.builtin.set_fact:
cnpg_release_series: "{{ (cnpg_version.split('.')[:2]) | join('.') }}"
- name: Build CNPG manifest URL
ansible.builtin.set_fact:
cnpg_manifest_url: "https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-{{ cnpg_release_series }}/releases/cnpg-{{ cnpg_version }}.yaml"
- name: Load k3s server URL from prole.cfg
ansible.builtin.set_fact:
cnpg_prole_k3s_server_cfg: >-
{{ lookup(
'ansible.builtin.ini',
'PROLE_K3S_SERVER section=Global file=' ~ (role_path ~ '/../../../conf/k3s.cfg'),
default='',
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else '' }}
- name: Resolve k3s API URL for kubectl
ansible.builtin.set_fact:
cnpg_k3s_server_url_raw: >-
{{ (k3s_server_url | default('')) if (k3s_server_url | default('') | length > 0)
else (cnpg_prole_k3s_server_cfg | default('')) if (cnpg_prole_k3s_server_cfg | default('') | length > 0)
else 'https://' + inventory_hostname + ':6443' }}
- name: Normalize k3s API URL for kubectl
ansible.builtin.set_fact:
cnpg_k3s_server_url: "{{ cnpg_k3s_server_url_raw if cnpg_k3s_server_url_raw.startswith('http') else 'https://' + cnpg_k3s_server_url_raw }}"
- name: Check for local k3s kubeconfig
ansible.builtin.stat:
path: /etc/rancher/k3s/k3s.yaml
register: cnpg_kubeconfig_stat
- name: Ensure k3s kubeconfig for kubectl on hosts
ansible.builtin.copy:
dest: /etc/rancher/k3s/k3s.yaml
owner: root
group: "{{ k3s_kubeconfig_group | default('root') }}"
mode: "{{ k3s_write_kubeconfig_mode | default('0640') }}"
content: |
apiVersion: v1
kind: Config
clusters:
- cluster:
server: {{ cnpg_k3s_server_url }}
insecure-skip-tls-verify: true
name: prole-k3s
contexts:
- context:
cluster: prole-k3s
user: prole-k3s
name: prole-k3s
current-context: prole-k3s
users:
- name: prole-k3s
user:
token: {{ k3s_token }}
when:
- not cnpg_kubeconfig_stat.stat.exists
- cnpg_k3s_server_url | length > 0
- k3s_token | default('') | length > 0
- name: Check k3s API availability for CNPG
ansible.builtin.command: kubectl get nodes
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: cnpg_k3s_api_check
changed_when: false
failed_when: false
- name: Map kubectl-cnpg architecture
ansible.builtin.set_fact:
cnpg_arch_map:
x86_64: x86_64
amd64: x86_64
aarch64: arm64
arm64: arm64
ppc64le: ppc64le
s390x: s390x
- name: Set cnpg architecture and supported arches
ansible.builtin.set_fact:
cnpg_arch: "{{ cnpg_arch_map.get(ansible_architecture, ansible_architecture) }}"
cnpg_supported_arches:
- x86_64
- arm64
- ppc64le
- s390x
- name: Check kubectl-cnpg version
ansible.builtin.command: kubectl cnpg version
register: cnpg_plugin_version_check
changed_when: false
failed_when: false
- name: Parse installed kubectl-cnpg version
ansible.builtin.set_fact:
cnpg_plugin_installed_version: "{{ (cnpg_plugin_version_check.stdout ~ ' ' ~ cnpg_plugin_version_check.stderr) | regex_search('Version:([0-9]+\\.[0-9]+\\.[0-9]+)', '\\1') | default('') }}"
- name: Build kubectl-cnpg download URL
ansible.builtin.set_fact:
cnpg_plugin_asset: "kubectl-cnpg_{{ cnpg_version }}_linux_{{ cnpg_arch }}.tar.gz"
cnpg_plugin_url: "https://github.com/cloudnative-pg/cloudnative-pg/releases/download/v{{ cnpg_version }}/kubectl-cnpg_{{ cnpg_version }}_linux_{{ cnpg_arch }}.tar.gz"
when: cnpg_arch in cnpg_supported_arches
- name: Download kubectl-cnpg
ansible.builtin.get_url:
url: "{{ cnpg_plugin_url }}"
dest: "/tmp/{{ cnpg_plugin_asset }}"
mode: "0644"
when:
- cnpg_arch in cnpg_supported_arches
- cnpg_plugin_installed_version == '' or ((k3s_cnpg_upgrade | default(false)) | bool and cnpg_plugin_installed_version != cnpg_version)
- name: Install kubectl-cnpg
ansible.builtin.unarchive:
src: "/tmp/{{ cnpg_plugin_asset }}"
dest: /usr/local/bin
remote_src: true
when:
- cnpg_arch in cnpg_supported_arches
- cnpg_plugin_installed_version == '' or ((k3s_cnpg_upgrade | default(false)) | bool and cnpg_plugin_installed_version != cnpg_version)
- name: Ensure kubectl-cnpg is executable
ansible.builtin.file:
path: /usr/local/bin/kubectl-cnpg
mode: "0755"
when:
- cnpg_arch in cnpg_supported_arches
- cnpg_plugin_installed_version == '' or ((k3s_cnpg_upgrade | default(false)) | bool and cnpg_plugin_installed_version != cnpg_version)
- name: Check for CNPG controller deployment
ansible.builtin.command: kubectl get deployment -n cnpg-system cnpg-controller-manager
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: cnpg_controller_deploy
changed_when: false
failed_when: false
when: cnpg_k3s_api_check.rc == 0
- name: Apply CNPG operator manifest (install only; pinned)
ansible.builtin.command: kubectl apply --server-side -f {{ cnpg_manifest_url }}
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: cnpg_apply
changed_when: "'created' in cnpg_apply.stdout or 'configured' in cnpg_apply.stdout"
when:
- cnpg_k3s_api_check.rc == 0
- cnpg_controller_deploy.rc != 0
- name: Notify explicit CNPG operator upgrade handler (opt-in)
ansible.builtin.debug:
msg: "CNPG operator upgrade requested (k3s_cnpg_upgrade=true). Upgrading to pinned CNPG_VERSION={{ cnpg_version }} via handler."
changed_when: true
notify:
- Upgrade CNPG operator
when:
- cnpg_k3s_api_check.rc == 0
- cnpg_controller_deploy.rc == 0
- (k3s_cnpg_upgrade | default(false)) | bool
- name: Verify CNPG controller rollout (new install)
ansible.builtin.command: kubectl rollout status deployment -n cnpg-system cnpg-controller-manager --timeout=60s
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
register: cnpg_rollout_check
changed_when: false
failed_when: false
when:
- cnpg_k3s_api_check.rc == 0
- cnpg_controller_deploy.rc != 0
- name: Restart CNPG controller if rollout failed (new install; possibly stuck on tainted node)
ansible.builtin.command: kubectl rollout restart deployment -n cnpg-system cnpg-controller-manager
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
when:
- cnpg_k3s_api_check.rc == 0
- cnpg_controller_deploy.rc != 0
- cnpg_rollout_check.rc != 0
changed_when: true
- name: Final CNPG controller rollout check (new install)
ansible.builtin.command: kubectl rollout status deployment -n cnpg-system cnpg-controller-manager --timeout=300s
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
changed_when: false
when:
- cnpg_k3s_api_check.rc == 0
- cnpg_controller_deploy.rc != 0

View File

@ -1,302 +0,0 @@
---
- name: Skip k3s configuration when disabled
ansible.builtin.meta: end_host
when: not (k3s_enabled | default(true) | bool)
- name: Skip k3s configuration when state is absent
ansible.builtin.meta: end_host
when: k3s_state == "absent"
- name: Set k3s service name
ansible.builtin.set_fact:
k3s_service_name: "{{ 'k3s' if k3s_role == 'server' else 'k3s-agent' }}"
- name: Check if controller has conf/k3s.cfg (optional)
ansible.builtin.stat:
path: "{{ role_path }}/../../../conf/k3s.cfg"
register: _k3s_prole_cfg_stat
delegate_to: localhost
- name: Load service namespace from k3s.cfg (controller)
ansible.builtin.set_fact:
k3s_prole_service_namespace_cfg: >-
{{ lookup(
'ansible.builtin.ini',
'SERVICE_NAMESPACE section=Global file=' ~ (role_path ~ '/../../../conf/k3s.cfg'),
default='',
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else '' }}
changed_when: false
- name: Load service hostname from k3s.cfg (controller)
ansible.builtin.set_fact:
k3s_prole_service_hostname_user_cfg: >-
{{ lookup(
'ansible.builtin.ini',
'SERVICE_HOSTNAME section=User file=' ~ (role_path ~ '/../../../conf/k3s.cfg'),
default='',
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else '' }}
k3s_prole_service_hostname_global_cfg: >-
{{ lookup(
'ansible.builtin.ini',
'SERVICE_HOSTNAME section=Global file=' ~ (role_path ~ '/../../../conf/k3s.cfg'),
default='',
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else '' }}
changed_when: false
- name: Normalize service hostname variable
ansible.builtin.set_fact:
k3s_service_hostname: >-
{{ (k3s_prole_service_hostname_user_cfg | default('') | trim)
if ((k3s_prole_service_hostname_user_cfg | default('') | trim) | length > 0
and (k3s_prole_service_hostname_user_cfg | trim) != '${SERVICE_HOSTNAME}')
else (k3s_prole_service_hostname_global_cfg | default('') | trim)
if ((k3s_prole_service_hostname_global_cfg | default('') | trim) | length > 0
and (k3s_prole_service_hostname_global_cfg | trim) != '${SERVICE_HOSTNAME}')
else (k3s_service_hostname | default('svc.prole.org')) }}
changed_when: false
- name: Resolve Kong namespaces from k3s.cfg (avoid implicit default namespace)
ansible.builtin.set_fact:
_k3s_service_namespace_resolved: >-
{{ (k3s_prole_service_namespace_cfg | default(''))
if (k3s_prole_service_namespace_cfg | default('') | length > 0 and k3s_prole_service_namespace_cfg != '${SERVICE_NAMESPACE}')
else 'default' }}
changed_when: false
- name: Normalize Kong namespace variables
ansible.builtin.set_fact:
k3s_kong_namespace: >-
{{ (k3s_kong_namespace | default(''))
if (k3s_kong_namespace | default('') | length > 0 and k3s_kong_namespace != '${SERVICE_NAMESPACE}')
else _k3s_service_namespace_resolved }}
changed_when: false
- name: Normalize svc-check Kong namespace variable
ansible.builtin.set_fact:
k3s_svc_check_kong_namespace: >-
{{ (k3s_svc_check_kong_namespace | default(''))
if (k3s_svc_check_kong_namespace | default('') | length > 0 and k3s_svc_check_kong_namespace != '${SERVICE_NAMESPACE}')
else k3s_kong_namespace }}
changed_when: false
- name: Validate resolved Kong namespace variables
ansible.builtin.assert:
that:
- k3s_kong_namespace | length > 0
- k3s_svc_check_kong_namespace | length > 0
fail_msg: >-
Resolved Kong namespace variables are empty. Ensure `SERVICE_NAMESPACE` is set in `conf/k3s.cfg`
or explicitly set `k3s_kong_namespace` / `k3s_svc_check_kong_namespace` in Ansible inventory.
- name: Pre-stage critical images (registry:2, kong, etc.)
ansible.builtin.include_tasks: prestage_images.yml
when:
- k3s_state == "present"
- k3s_prestage_images | bool
tags: [images]
- name: Create ArgoCD hostPath directories on myrddin.prole.org (/synology/d001)
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0777"
loop:
- /synology/d001/argocd
- /synology/d001/argocd/home
- /synology/d001/argocd/tmp
when:
- k3s_state == "present"
- inventory_hostname == 'myrddin.prole.org'
- name: Create ArgoCD hostPath directories on merlin.prole.org (/synology/d002)
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0777"
loop:
- /synology/d002/argocd
- /synology/d002/argocd/data
- /synology/d002/argocd/tmp
when:
- k3s_state == "present"
- inventory_hostname == 'merlin.prole.org'
- name: Create ArgoCD hostPath directories on pi.prole.org (/synology/d003)
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: "0777"
loop:
- /synology/d003/argocd
- /synology/d003/argocd/gpg-keyring
- /synology/d003/argocd/tmp
- /synology/d003/argocd/helm-working-dir
- /synology/d003/argocd/var-files
- /synology/d003/argocd/plugins
when:
- k3s_state == "present"
- inventory_hostname == 'pi.prole.org'
- name: Wait for k3s Kubernetes API to become ready
ansible.builtin.command: k3s kubectl get --raw='/readyz'
register: _k3s_readyz
changed_when: false
retries: 60
delay: 5
until: _k3s_readyz.rc == 0
when:
- k3s_state == "present"
- k3s_role == 'server'
run_once: true
- name: Load optional workloads threshold from prole.cfg (controller)
ansible.builtin.set_fact:
k3s_optional_workloads_min_nodes_cfg: >-
{{ lookup(
'ansible.builtin.ini',
'OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES section=Global file=' ~ (role_path ~ '/../../../conf/k3s.cfg'),
default='',
errors='ignore'
) if _k3s_prole_cfg_stat.stat.exists else '' }}
changed_when: false
when:
- k3s_state == "present"
- k3s_role == 'server'
run_once: true
- name: Validate optional-workloads policy config is present
ansible.builtin.assert:
that:
- _k3s_prole_cfg_stat.stat.exists
- (k3s_optional_workloads_min_nodes_cfg | string | trim | length) > 0
- (k3s_optional_workloads_min_nodes_cfg | int) > 0
fail_msg: >-
Missing or invalid optional-workloads policy configuration.
Expected [Global] OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES in conf/k3s.cfg
(got '{{ k3s_optional_workloads_min_nodes_cfg | default('') }}').
changed_when: false
when:
- k3s_state == "present"
- k3s_role == 'server'
run_once: true
- name: Query cluster nodes for optional-workloads policy
ansible.builtin.command: k3s kubectl get nodes --no-headers
register: _k3s_nodes_list
changed_when: false
failed_when: false
when:
- k3s_state == "present"
- k3s_role == 'server'
run_once: true
- name: Log optional-workloads policy node query failure (treat as insufficient capacity)
ansible.builtin.debug:
msg: >-
Could not query Kubernetes nodes for optional-workloads policy.
Treating as ready_schedulable_nodes=0 so optional workloads will be disabled for this run.
rc={{ _k3s_nodes_list.rc | default('n/a') }} stderr={{ _k3s_nodes_list.stderr | default('') | trim }}
when:
- k3s_state == "present"
- k3s_role == 'server'
- _k3s_nodes_list is defined
- (_k3s_nodes_list.rc | default(1) | int) != 0
run_once: true
- name: Evaluate optional-workloads policy (ready + schedulable)
ansible.builtin.include_tasks: optional_workloads_policy_eval.yml
when:
- k3s_state == "present"
- k3s_role == 'server'
run_once: true
- name: Log optional-workloads policy decision
ansible.builtin.debug:
msg: >-
optional_workloads_allowed={{ optional_workloads_allowed }}
(ready_schedulable_nodes={{ ready_schedulable_nodes }} min_required={{ optional_workloads_min_ready_schedulable_nodes }})
when:
- k3s_state == "present"
- k3s_role == 'server'
run_once: true
- name: Apply merlin-local-iscsi StorageClass (monitoring)
ansible.builtin.shell: |
k3s kubectl apply --validate=false -f - <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: merlin-local-iscsi
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
EOF
register: _merlin_local_iscsi_apply
changed_when: >-
(_merlin_local_iscsi_apply.stdout | default('') | lower) is search('configured')
or (_merlin_local_iscsi_apply.stdout | default('') | lower) is search('created')
failed_when: false
when:
- k3s_state == "present"
- k3s_role == 'server'
- optional_workloads_allowed | default(false) | bool
run_once: true
- name: Log monitoring StorageClass apply failure (non-fatal)
ansible.builtin.debug:
msg: >-
Failed to apply monitoring StorageClass (non-fatal): rc={{ _merlin_local_iscsi_apply.rc | default('n/a') }}
stderr={{ _merlin_local_iscsi_apply.stderr | default('') | trim }}
when:
- k3s_state == "present"
- k3s_role == 'server'
- optional_workloads_allowed | default(false) | bool
- _merlin_local_iscsi_apply is defined
- (_merlin_local_iscsi_apply.rc | default(0) | int) != 0
run_once: true
- name: Skip monitoring StorageClass when optional workloads are disallowed
ansible.builtin.debug:
msg: >-
Skipping monitoring StorageClass because optional workloads are disallowed:
optional_workloads_allowed={{ optional_workloads_allowed | default(false) }}
(ready_schedulable_nodes={{ ready_schedulable_nodes | default('unknown') }} min_required={{ optional_workloads_min_ready_schedulable_nodes | default('unknown') }})
when:
- k3s_state == "present"
- k3s_role == 'server'
- not (optional_workloads_allowed | default(false) | bool)
run_once: true
- name: Fetch kubeconfig to controller for controller-side installs
ansible.builtin.include_tasks: fetch_kubeconfig.yml
when:
- k3s_state == "present"
- k3s_role == 'server'
run_once: true
- name: Ensure CloudNative-PG operator and kubectl plugin
ansible.builtin.include_tasks: cnpg.yml
when: k3s_state == "present"
- name: Ensure cert-manager is installed for ACME
ansible.builtin.include_tasks: certmgr.yml
when:
- k3s_state == "present"
- k3s_role == 'server'
- name: Configure ACME issuer and certificate for service hostname
ansible.builtin.include_tasks: acme_cert.yml
when:
- k3s_state == "present"
- k3s_role == 'server'
- name: Ensure Kong is present and refreshed
ansible.builtin.include_tasks: kong.yml
when:
- k3s_state == "present"
- k3s_role == 'server'

View File

@ -1,109 +0,0 @@
---
- name: Build kubeconfig fetch candidates
ansible.builtin.set_fact:
k3s_kubeconfig_source_candidates: "{{ groups['k3s_hosts'] | default([inventory_hostname]) }}"
- name: Initialize kubeconfig source host
ansible.builtin.set_fact:
k3s_kubeconfig_source_host: ""
- name: Select k3s init server as kubeconfig source (preferred)
ansible.builtin.set_fact:
k3s_kubeconfig_source_host: "{{ item }}"
loop: "{{ k3s_kubeconfig_source_candidates }}"
when:
- (k3s_kubeconfig_source_host | length) == 0
- hostvars[item].k3s_role | default('agent') == 'server'
- hostvars[item].k3s_cluster_init | default(false) | bool
- name: Fallback to first k3s server as kubeconfig source
ansible.builtin.set_fact:
k3s_kubeconfig_source_host: "{{ item }}"
loop: "{{ k3s_kubeconfig_source_candidates }}"
when:
- (k3s_kubeconfig_source_host | length) == 0
- hostvars[item].k3s_role | default('agent') == 'server'
- name: Final fallback to current host as kubeconfig source
ansible.builtin.set_fact:
k3s_kubeconfig_source_host: "{{ inventory_hostname }}"
when: (k3s_kubeconfig_source_host | length) == 0
- name: Require kubeconfig source host
ansible.builtin.assert:
that:
- (k3s_kubeconfig_source_host | length) > 0
fail_msg: "Unable to select a k3s kubeconfig source host."
- name: Determine kubeconfig API server URL
ansible.builtin.set_fact:
k3s_kubeconfig_api_server: >-
{{ (k3s_server_url | trim) if (k3s_server_url | default('') | trim | length) > 0
else 'https://' ~ k3s_kubeconfig_source_host ~ ':6443' }}
- name: Read k3s kubeconfig from source node
ansible.builtin.slurp:
src: /etc/rancher/k3s/k3s.yaml
register: _k3s_kubeconfig_raw
delegate_to: "{{ k3s_kubeconfig_source_host }}"
become: true
- name: Rewrite kubeconfig server and context to primary endpoint
ansible.builtin.set_fact:
k3s_kubeconfig_rendered: >-
{{ _k3s_kubeconfig_raw.content
| b64decode
| regex_replace('server:\s*https?://\S+', 'server: ' ~ k3s_kubeconfig_api_server)
| regex_replace('name:\s*default', 'name: ' ~ (k3s_kubeconfig_context | default('prole-service-cluster')))
| regex_replace('context:\s*default', 'context: ' ~ (k3s_kubeconfig_context | default('prole-service-cluster')))
| regex_replace('user:\s*default', 'user: ' ~ (k3s_kubeconfig_context | default('prole-service-cluster')))
| regex_replace('cluster:\s*default', 'cluster: ' ~ (k3s_kubeconfig_context | default('prole-service-cluster')))
| regex_replace('current-context:\s*default', 'current-context: ' ~ (k3s_kubeconfig_context | default('prole-service-cluster'))) }}
- name: Validate rendered kubeconfig YAML (controller-side scripts depend on this)
ansible.builtin.set_fact:
_k3s_kubeconfig_parsed: "{{ k3s_kubeconfig_rendered | from_yaml }}"
changed_when: false
- name: Require kubeconfig to contain required keys
ansible.builtin.assert:
that:
- _k3s_kubeconfig_parsed is mapping
- (_k3s_kubeconfig_parsed.clusters | default([])) | length > 0
- (_k3s_kubeconfig_parsed.contexts | default([])) | length > 0
- (_k3s_kubeconfig_parsed.users | default([])) | length > 0
- (_k3s_kubeconfig_parsed['current-context'] | default('') | length) > 0
fail_msg: >-
Rendered kubeconfig is invalid or missing required keys. Refusing to save
kubeconfig to the controller.
- name: Define controller kubeconfig destinations
ansible.builtin.set_fact:
k3s_kubeconfig_controller_project_path: "{{ playbook_dir }}/../../prole-k3s.kubeconfig"
k3s_kubeconfig_controller_service_path: "{{ playbook_dir }}/../../etc/secrets/k3s.kubeconfig"
changed_when: false
- name: Ensure controller kubeconfig directory exists (service path)
ansible.builtin.file:
path: "{{ k3s_kubeconfig_controller_service_path | dirname }}"
state: directory
mode: "0700"
delegate_to: localhost
become: false
- name: Save kubeconfig locally for controller-side init scripts (project root)
ansible.builtin.copy:
content: "{{ k3s_kubeconfig_rendered }}"
dest: "{{ k3s_kubeconfig_controller_project_path }}"
mode: "0600"
delegate_to: localhost
become: false
- name: Save kubeconfig locally for controller-side init scripts (etc/secrets)
ansible.builtin.copy:
content: "{{ k3s_kubeconfig_rendered }}"
dest: "{{ k3s_kubeconfig_controller_service_path }}"
mode: "0600"
delegate_to: localhost
become: false

View File

@ -1,42 +0,0 @@
---
- name: Install prole svc firewall enforcement script
ansible.builtin.template:
src: prole-svc-iptables-allow.sh.j2
dest: /usr/local/sbin/prole-svc-iptables-allow
owner: root
group: root
mode: "0755"
when:
- k3s_firewall_svc_allow_enabled | default(true) | bool
- ansible_service_mgr | default('') == 'systemd'
- name: Install prole svc firewall systemd unit
ansible.builtin.template:
src: prole-svc-iptables-allow.service.j2
dest: /etc/systemd/system/prole-svc-iptables-allow.service
owner: root
group: root
mode: "0644"
register: _prole_svc_fw_unit
when:
- k3s_firewall_svc_allow_enabled | default(true) | bool
- ansible_service_mgr | default('') == 'systemd'
- name: Reload systemd daemon (svc firewall unit)
ansible.builtin.systemd:
daemon_reload: true
when:
- k3s_firewall_svc_allow_enabled | default(true) | bool
- ansible_service_mgr | default('') == 'systemd'
- _prole_svc_fw_unit is defined
- _prole_svc_fw_unit.changed
- name: Enable and run prole svc firewall unit
ansible.builtin.systemd:
name: prole-svc-iptables-allow.service
enabled: true
state: restarted
when:
- k3s_firewall_svc_allow_enabled | default(true) | bool
- ansible_service_mgr | default('') == 'systemd'

Some files were not shown because too many files have changed in this diff Show More