feat(proxmox): add onboarding playbooks and host variables for Proxmox VE management

This commit is contained in:
Nathan 2026-04-13 20:16:57 -04:00
parent ef875a78cc
commit 49b3f3a652
10 changed files with 506 additions and 2 deletions

View File

@ -0,0 +1,27 @@
---
# Host-specific variables for pve01
# IP: 10.0.0.201
# Hardware Details
hardware:
platform: physical_server
role: hypervisor
architecture: x86_64
# Operating System
os:
distribution: Proxmox VE
type: debian_based
# Node Role
node_role: proxmox_hypervisor
is_hypervisor: true
# Proxmox Configuration
proxmox:
post_install_enabled: true
disable_subscription_nag: true
disable_enterprise_repo: true
enable_no_subscription_repo: true
run_dist_upgrade: false # Manual control for first onboarding
reboot_after: false # Manual control for first onboarding

View File

@ -25,6 +25,9 @@ waldorf ansible_host=10.0.0.251 ansible_user=chester
# ============================================================================= # =============================================================================
# Platform Groups # Platform Groups
# ============================================================================= # =============================================================================
[proxmox_cluster]
pve01 ansible_host=10.0.0.201 ansible_user=root
[physical_servers] [physical_servers]
heimdall ansible_host=10.0.0.151 ansible_user=chester heimdall ansible_host=10.0.0.151 ansible_user=chester
waldorf ansible_host=10.0.0.251 ansible_user=chester waldorf ansible_host=10.0.0.251 ansible_user=chester

View File

@ -27,7 +27,7 @@
- name: Ensure .ssh directory exists - name: Ensure .ssh directory exists
ansible.builtin.file: ansible.builtin.file:
path: "{{ ansible_env.HOME }}/.ssh" path: "/home/{{ ansible_user }}/.ssh"
state: directory state: directory
mode: "0700" mode: "0700"
owner: "{{ ansible_user }}" owner: "{{ ansible_user }}"
@ -88,7 +88,7 @@
ansible.builtin.debug: ansible.builtin.debug:
msg: >- msg: >-
{% if nfs_mount.stat.exists %} {% if nfs_mount.stat.exists %}
✅ /mnt/appdata exists ({{ 'mounted' if nfs_mount.stat.ismount else 'not mounted' }}) ✅ /mnt/appdata exists
{% else %} {% else %}
⚠️ /mnt/appdata does NOT exist ⚠️ /mnt/appdata does NOT exist
{% endif %} {% endif %}

View File

@ -0,0 +1,86 @@
---
# Proxmox Node Onboarding Playbook
# Purpose: Onboard Proxmox VE hosts with post-install configuration
# Usage: ansible-playbook playbooks/onboard-proxmox.yml -k --limit pve01
# (-k prompts for root SSH password on first run)
- name: Onboard Proxmox VE node
hosts: proxmox_cluster
gather_facts: true
become: false # Already connecting as root
tasks:
- name: Display target host information
ansible.builtin.debug:
msg: |
Onboarding {{ inventory_hostname }}
IP: {{ ansible_host }}
User: {{ ansible_user }}
- name: Ensure .ssh directory exists for root
ansible.builtin.file:
path: /root/.ssh
state: directory
mode: "0700"
owner: root
group: root
- name: Deploy watchtower SSH public key to root
ansible.builtin.authorized_key:
user: root
state: present
key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ryXcRsMITcIW+Rc0t3Qou7XGfyIeihLR2PInySogp ansible@watchtower"
comment: "ansible@watchtower"
- name: Detect Proxmox VE version
ansible.builtin.command: pveversion
register: pve_version_check
changed_when: false
failed_when: false
- name: Display Proxmox version
ansible.builtin.debug:
msg: |
{% if pve_version_check.rc == 0 %}
✅ Proxmox VE detected: {{ pve_version_check.stdout }}
{% else %}
⚠️ Could not detect Proxmox VE (pveversion command failed)
{% endif %}
- name: Verify Python 3 is available
ansible.builtin.command: python3 --version
register: python_version
changed_when: false
- name: Display Python version
ansible.builtin.debug:
msg: "Python: {{ python_version.stdout }}"
- name: Run Proxmox post-install configuration
ansible.builtin.include_role:
name: proxmox_post_install
vars:
proxmox_post_install_enabled: true
proxmox_disable_subscription_nag: true
proxmox_disable_pve_enterprise: true
proxmox_enable_pve_no_subscription: true
proxmox_fix_sources: true
proxmox_fix_ceph_repos: true
proxmox_run_dist_upgrade: false # Skip for initial onboarding
proxmox_reboot_after: false # Manual control
when: pve_version_check.rc == 0
- name: Display onboarding summary
ansible.builtin.debug:
msg:
- "=========================================="
- "Proxmox Onboarding Complete: {{ inventory_hostname }}"
- "=========================================="
- "✅ SSH key deployed to root"
- "✅ Subscription nag removed"
- "✅ Repositories configured"
- ""
- "Next steps:"
- " • Test connectivity: ansible pve01 -m ping"
- " • Update system: ansible pve01 -m apt -a 'upgrade=dist update_cache=yes'"
- " • Review logs and reboot if kernel/system updates applied"

View File

@ -0,0 +1,32 @@
---
# Default variables for proxmox_post_install role
# These defaults assume you "approve all risks" similar to the original script
# General
proxmox_post_install_enabled: true
# Behavior toggles roughly mirroring the whiptail prompts
proxmox_fix_sources: true
proxmox_disable_pve_enterprise: true
proxmox_enable_pve_no_subscription: true
proxmox_fix_ceph_repos: true
proxmox_add_pvetest_repo_disabled: true
# Subscription nag removal
proxmox_disable_subscription_nag: true
# HA behavior
proxmox_enable_ha: false # default: do not auto-enable HA on fresh node
proxmox_disable_ha_on_single_node: true
proxmox_disable_corosync_on_single_node: true
# Update & reboot
proxmox_run_dist_upgrade: true
proxmox_reboot_after: true
# PVE version restrictions (mirrors the script: 8.0-8.9.x and 9.0-9.1.x)
proxmox_supported_major_versions: [8, 9]
proxmox_8_min_minor: 0
proxmox_8_max_minor: 9
proxmox_9_min_minor: 0
proxmox_9_max_minor: 1

View File

@ -0,0 +1,55 @@
---
# Main entrypoint for proxmox_post_install role
- name: "Check that role is enabled"
ansible.builtin.meta: end_play
when: not proxmox_post_install_enabled
- name: "Detect Proxmox VE version (pveversion)"
ansible.builtin.command: "pveversion"
register: proxmox_pveversion_cmd
changed_when: false
- name: "Parse Proxmox VE version"
ansible.builtin.set_fact:
proxmox_pve_version_full: "{{ proxmox_pveversion_cmd.stdout | trim }}"
# pveversion output: "pve-manager/9.1.1/42db4a6cf33dac83" - version is at index 1
proxmox_pve_version: "{{ (proxmox_pveversion_cmd.stdout | trim).split('/')[1] }}"
proxmox_pve_major: "{{ (proxmox_pveversion_cmd.stdout | trim).split('/')[1].split('.')[0] | int }}"
proxmox_pve_minor: "{{ (proxmox_pveversion_cmd.stdout | trim).split('/')[1].split('.')[1] | int }}"
- name: "Fail if Proxmox VE major version is unsupported"
ansible.builtin.fail:
msg: >-
Unsupported Proxmox VE major version: {{ proxmox_pve_major }}.
Supported: 8.08.9.x and 9.09.1.x (mirrors upstream post-pve-install.sh).
when: proxmox_pve_major not in proxmox_supported_major_versions
- name: "Fail if Proxmox VE 8 minor version unsupported"
ansible.builtin.fail:
msg: >-
Unsupported Proxmox 8 version {{ proxmox_pve_version }}.
Supported minor range: {{ proxmox_8_min_minor }}{{ proxmox_8_max_minor }}.
when:
- proxmox_pve_major == 8
- proxmox_pve_minor < proxmox_8_min_minor or proxmox_pve_minor > proxmox_8_max_minor
- name: "Fail if Proxmox VE 9 minor version unsupported"
ansible.builtin.fail:
msg: >-
Unsupported Proxmox 9 version {{ proxmox_pve_version }}.
Supported minor range: {{ proxmox_9_min_minor }}{{ proxmox_9_max_minor }}.
when:
- proxmox_pve_major == 9
- proxmox_pve_minor < proxmox_9_min_minor or proxmox_pve_minor > proxmox_9_max_minor
- name: "Include version-specific tasks for PVE 8"
ansible.builtin.import_tasks: pve8.yml
when: proxmox_pve_major == 8
- name: "Include version-specific tasks for PVE 9"
ansible.builtin.import_tasks: pve9.yml
when: proxmox_pve_major == 9
- name: "Common post-routines (nag, HA, update, reboot)"
ansible.builtin.import_tasks: post_common.yml

View File

@ -0,0 +1,76 @@
---
# Common post-routines for PVE 8 and 9: subscription nag, HA, updates, reboot
- name: "Deploy subscription nag removal script"
ansible.builtin.template:
src: pve-remove-nag.sh.j2
dest: /usr/local/bin/pve-remove-nag.sh
owner: root
group: root
mode: '0755'
when: proxmox_disable_subscription_nag
- name: "Configure dpkg Post-Invoke hook to run nag removal script"
ansible.builtin.copy:
dest: /etc/apt/apt.conf.d/no-nag-script
owner: root
group: root
mode: '0644'
content: |
DPkg::Post-Invoke { "/usr/local/bin/pve-remove-nag.sh"; };
when: proxmox_disable_subscription_nag
- name: "Remove subscription nag dpkg hook if disabled via vars"
ansible.builtin.file:
path: /etc/apt/apt.conf.d/no-nag-script
state: absent
when: not proxmox_disable_subscription_nag
- name: "Ensure proxmox-widget-toolkit is reinstalled (like script)"
ansible.builtin.apt:
name: proxmox-widget-toolkit
state: latest
update_cache: false
force: true
register: proxmox_widget_reinstall
failed_when: false
- name: "Optionally enable HA services on cluster nodes"
ansible.builtin.service:
name: "{{ item }}"
state: started
enabled: true
loop:
- pve-ha-lrm
- pve-ha-crm
- corosync
when: proxmox_enable_ha
- name: "Optionally disable HA services on single-node setups"
ansible.builtin.service:
name: "{{ item }}"
state: stopped
enabled: false
loop:
- pve-ha-lrm
- pve-ha-crm
when: proxmox_disable_ha_on_single_node
- name: "Optionally disable Corosync on single-node setups"
ansible.builtin.service:
name: corosync
state: stopped
enabled: false
when: proxmox_disable_corosync_on_single_node
- name: "Run apt dist-upgrade (like original script)"
ansible.builtin.apt:
update_cache: true
upgrade: dist
when: proxmox_run_dist_upgrade
- name: "Reboot Proxmox VE when requested"
ansible.builtin.reboot:
msg: "Rebooting after post-install routines (Ansible)."
reboot_timeout: 1800
when: proxmox_reboot_after

View File

@ -0,0 +1,57 @@
---
# Proxmox VE 8.x (Debian 12 / bookworm) sources and repo configuration
- name: "Configure Debian bookworm APT sources (if enabled)"
ansible.builtin.copy:
dest: /etc/apt/sources.list
owner: root
group: root
mode: '0644'
content: |
deb http://deb.debian.org/debian bookworm main contrib
deb http://deb.debian.org/debian bookworm-updates main contrib
deb http://security.debian.org/debian-security bookworm-security main contrib
when: proxmox_fix_sources
- name: "Disable pve-enterprise repository (list file) on 8.x"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/pve-enterprise.list
owner: root
group: root
mode: '0644'
content: |
# deb https://enterprise.proxmox.com/debian/pve bookworm pve-enterprise
when: proxmox_disable_pve_enterprise
- name: "Enable pve-no-subscription repository on 8.x"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/pve-install-repo.list
owner: root
group: root
mode: '0644'
content: |
deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription
when: proxmox_enable_pve_no_subscription
- name: "Configure Ceph repositories for Proxmox VE 8.x"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/ceph.list
owner: root
group: root
mode: '0644'
content: |
# deb https://enterprise.proxmox.com/debian/ceph-quincy bookworm enterprise
# deb http://download.proxmox.com/debian/ceph-quincy bookworm no-subscription
# deb https://enterprise.proxmox.com/debian/ceph-reef bookworm enterprise
# deb http://download.proxmox.com/debian/ceph-reef bookworm no-subscription
when: proxmox_fix_ceph_repos
- name: "Add disabled pvetest repository for 8.x"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/pvetest-for-beta.list
owner: root
group: root
mode: '0644'
content: |
# deb http://download.proxmox.com/debian/pve bookworm pvetest
when: proxmox_add_pvetest_repo_disabled

View File

@ -0,0 +1,123 @@
---
# Proxmox VE 9.x (Debian 13 / trixie) sources and repo configuration using deb822
- name: "Find legacy .list APT source files on 9.x"
ansible.builtin.find:
paths:
- /etc/apt/sources.list.d
patterns: "*.list"
file_type: file
register: proxmox_legacy_list_files
- name: "Backup and disable entries in /etc/apt/sources.list (if any)"
ansible.builtin.copy:
src: /etc/apt/sources.list
dest: /etc/apt/sources.list.bak
owner: root
group: root
mode: '0644'
remote_src: true
when:
- proxmox_fix_sources
- ansible_facts['os_family'] is defined
ignore_errors: true
- name: "Comment legacy deb lines in /etc/apt/sources.list (bookworm/proxmox)"
ansible.builtin.replace:
path: /etc/apt/sources.list
regexp: '^(\s*deb\s+.*(proxmox|bookworm).*)$'
replace: '# Disabled by Proxmox Helper Ansible role \1'
when: proxmox_fix_sources
ignore_errors: true
- name: "Remove legacy .list files on 9.x when migrating to deb822"
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ proxmox_legacy_list_files.files | default([]) }}"
when: proxmox_fix_sources
- name: "Configure Debian Trixie deb822 sources for 9.x"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/debian.sources
owner: root
group: root
mode: '0644'
content: |
Types: deb
URIs: http://deb.debian.org/debian
Suites: trixie
Components: main contrib
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://security.debian.org/debian-security
Suites: trixie-security
Components: main contrib
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://deb.debian.org/debian
Suites: trixie-updates
Components: main contrib
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
when: proxmox_fix_sources
- name: "Ensure pve-enterprise deb822 source is disabled on 9.x"
ansible.builtin.blockinfile:
path: /etc/apt/sources.list.d/pve-enterprise.sources
create: true
owner: root
group: root
mode: '0644'
block: |
Types: deb
URIs: https://enterprise.proxmox.com/debian/pve
Suites: trixie
Components: pve-enterprise
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
Enabled: false
when: proxmox_disable_pve_enterprise
- name: "Configure pve-no-subscription deb822 source on 9.x"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/proxmox.sources
owner: root
group: root
mode: '0644'
content: |
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
when: proxmox_enable_pve_no_subscription
- name: "Configure Ceph deb822 source on 9.x (no-subscription)"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/ceph.sources
owner: root
group: root
mode: '0644'
content: |
Types: deb
URIs: http://download.proxmox.com/debian/ceph-squid
Suites: trixie
Components: no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
when: proxmox_fix_ceph_repos
- name: "Add disabled pve-test deb822 source on 9.x"
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/pve-test.sources
owner: root
group: root
mode: '0644'
content: |
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-test
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
Enabled: false
when: proxmox_add_pvetest_repo_disabled

View File

@ -0,0 +1,45 @@
#!/bin/sh
WEB_JS=/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
if [ -s "$WEB_JS" ] && ! grep -q NoMoreNagging "$WEB_JS"; then
echo "Patching Web UI nag..."
sed -i -e "/data\.status/ s/!//" -e "/data\.status/ s/active/NoMoreNagging/" "$WEB_JS"
fi
MOBILE_TPL=/usr/share/pve-yew-mobile-gui/index.html.tpl
MARKER="<!-- MANAGED BLOCK FOR MOBILE NAG -->"
if [ -f "$MOBILE_TPL" ] && ! grep -q "$MARKER" "$MOBILE_TPL"; then
echo "Patching Mobile UI nag..."
printf "%s\n" \
"$MARKER" \
"<script>" \
" function removeSubscriptionElements() {" \
" // --- Remove subscription dialogs ---" \
" const dialogs = document.querySelectorAll('dialog.pwt-outer-dialog');" \
" dialogs.forEach(dialog => {" \
" const text = (dialog.textContent || '').toLowerCase();" \
" if (text.includes('subscription')) {" \
" dialog.remove();" \
" console.log('Removed subscription dialog');" \
" }" \
" });" \
"" \
" // --- Remove subscription cards, but keep Reboot/Shutdown/Console ---" \
" const cards = document.querySelectorAll('.pwt-card.pwt-p-2.pwt-d-flex.pwt-interactive.pwt-justify-content-center');" \
" cards.forEach(card => {" \
" const text = (card.textContent || '').toLowerCase();" \
" const hasButton = card.querySelector('button');" \
" if (!hasButton && text.includes('subscription')) {" \
" card.remove();" \
" console.log('Removed subscription card');" \
" }" \
" });" \
" }" \
"" \
" const observer = new MutationObserver(removeSubscriptionElements);" \
" observer.observe(document.body, { childList: true, subtree: true });" \
" removeSubscriptionElements();" \
" setInterval(removeSubscriptionElements, 300);" \
" setTimeout(() => {observer.disconnect();}, 10000);" \
"</script>" \
"" >>"$MOBILE_TPL"
fi