Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Test
on:
- push
- pull_request

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
- run: pip install -r requirements.txt
- run: ansible-galaxy install -r requirements.yaml
- run: echo "$ANSIBLE_VAULT_PASSWORD" > .ansible_vault.pass
env:
ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
- run: ansible-lint
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.ssh/
.venv/
.ansible/
.ansible_vault.pass
.secrets/
697 changes: 697 additions & 0 deletions ansible.cfg

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions group_vars/all.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---

deploy_user: deploy
deploy_user_uid: 2000
deploy_user_groups:
- sudo
deploy_ssh_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIZak62dHFoQL3Co/XYs8SC6Lc/FnCT8xOiHu2SJAWO"

ansible_user: "{{ deploy_user }}"
9 changes: 9 additions & 0 deletions group_vars/pve.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---

pve_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
63616334353331326631363962316433346466623831616666303232303963666631316561333437
3464353131373335363630666631363738643539333139630a383665653666316362636366376233
61326530323732303565393030616533373834656530383430613965356339323166336531346266
6264666366616463640a653639383764323438663735323434666161383834343633626137306335
3235
3 changes: 3 additions & 0 deletions inventory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pve:
hosts:
nas:
6 changes: 6 additions & 0 deletions playbooks/bootstrap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: Bootstrap deploy user
hosts: all
gather_facts: false
roles:
- bootstrap
6 changes: 6 additions & 0 deletions playbooks/common.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---

- name: Apply common tweaks
hosts: all
roles:
- common
5 changes: 5 additions & 0 deletions playbooks/pve.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- name: Apply proxmox nodes
hosts: pve
roles:
- pve
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ansible==13.5.0
ansible-lint==26.4.0
proxmoxer==2.3.0
requests==2.33.1
4 changes: 4 additions & 0 deletions requirements.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
collections:
- name: community.proxmox
version: 2.0.0-beta1
3 changes: 3 additions & 0 deletions roles/bootstrap/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---

bootstrap_user: root
34 changes: 34 additions & 0 deletions roles/bootstrap/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
- name: Create and configure deploy user
vars:
ansible_user: "{{ bootstrap_user }}"
block:
- name: Setup
ansible.builtin.setup:

- name: Install sudo
ansible.builtin.package:
name: sudo
state: present

- name: Create deploy user
ansible.builtin.user:
name: "{{ deploy_user }}"
comment: Deploy user
uid: "{{ deploy_user_uid }}"
groups: "{{ deploy_user_groups }}"
state: present

- name: Add deploy SSH key to authorized keys
ansible.posix.authorized_key:
user: "{{ deploy_user }}"
key: "{{ deploy_ssh_public_key }}"
state: present

- name: Allow passwordless sudo for deploy user
community.general.sudoers:
name: deploy
user: "{{ deploy_user }}"
commands: ALL
nopassword: true
state: present
7 changes: 7 additions & 0 deletions roles/common/handlers/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---

- name: Restart ssh
become: true
ansible.builtin.systemd:
name: ssh
state: restarted
15 changes: 15 additions & 0 deletions roles/common/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---

- name: Harden SSH
become: true
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- regexp: '^#?PermitRootLogin'
line: PermitRootLogin no
- regexp: '^#?PasswordAuthentication'
line: PasswordAuthentication no
notify: Restart ssh
7 changes: 7 additions & 0 deletions roles/pve/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---

pve_vm_images:
- name: almalinux-10
vmid: 9001
template: AlmaLinux-10-GenericCloud-latest.x86_64_v2.qcow2
url: https://repo.almalinux.org/almalinux/10/cloud/x86_64_v2/images/AlmaLinux-10-GenericCloud-latest.x86_64_v2.qcow2
4 changes: 4 additions & 0 deletions roles/pve/handlers/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---

- name: Reboot
ansible.builtin.reboot:
231 changes: 231 additions & 0 deletions roles/pve/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
---
- name: Configure system packages
become: true
block:
- name: Disable paid repositories
ansible.builtin.deb822_repository:
name: "{{ item }}"
state: absent
loop:
- pve-enterprise
- ceph

- name: Add pve-no-subscription repository
ansible.builtin.deb822_repository:
name: pve-no-subscription
uris: http://download.proxmox.com/debian/pve
suites: '{{ ansible_facts["distribution_release"] }}'
components: pve-no-subscription
signed_by: /usr/share/keyrings/proxmox-archive-keyring.gpg
state: present

- name: Update packages
ansible.builtin.apt:
update_cache: true
upgrade: dist
notify: Reboot

- name: Install Intel microcode
when: ansible_facts["processor"] | lower is search('intel')
ansible.builtin.apt:
name: intel-microcode
state: present
notify: Reboot

- name: Install AMD microcode
when: ansible_facts["processor"] | lower is search('amd')
ansible.builtin.apt:
name: amd64-microcode
state: present
notify: Reboot

- name: Remove nag
become: true
block:
- name: Desktop
ansible.builtin.replace:
path: /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
regexp: 'res\.data\.status.*$'
replace: "res.data.status.toLowerCase() == 'NoMoreNagging'"

- name: Mobile
ansible.builtin.blockinfile:
path: /usr/share/pve-yew-mobile-gui/index.html.tpl
marker: "<!-- {mark} ANSIBLE MANAGED BLOCK FOR MOBILE NAG -->"
block: |
<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>
state: present

- name: Configure deploy API key
block:
- name: Create deploy user
community.proxmox.proxmox_user:
api_host: "{{ inventory_hostname }}"
api_user: root@pam
api_password: "{{ pve_password }}"
validate_certs: false
userid: "{{ deploy_user }}@pve"
tokens:
- tokenid: deploy
privsep: false
state: present
delegate_to: localhost
register: pve_deploy_user_result

- name: Save token to local file
vars:
pve_token_name: "{{ deploy_user }}"
ansible.builtin.template:
src: token.j2
dest: "../.secrets/pve/deploy_{{ inventory_hostname }}.txt"
mode: "0640"
when: "'secrets' in pve_deploy_user_result"
delegate_to: localhost

- name: Set token variable
ansible.builtin.set_fact:
pve_deploy_token: >-
{{ pve_deploy_user_result.secrets[deploy_user] if 'secrets' in pve_deploy_user_result
else lookup('ansible.builtin.file', '../.secrets/pve/deploy_%s.txt' % inventory_hostname) }}

- name: Add permissions to deploy user
community.proxmox.proxmox_access_acl:
api_host: "{{ inventory_hostname }}"
api_user: root@pam
api_password: "{{ pve_password }}"
validate_certs: false
ugid: "{{ deploy_user }}@pve"
roleid: Administrator
path: /
type: user
state: present
delegate_to: localhost

- name: Configure bridge VLANs
community.proxmox.proxmox_node_network:
api_host: "{{ inventory_hostname }}"
api_user: "{{ deploy_user }}@pve"
api_token_id: deploy
api_token_secret: "{{ pve_deploy_token }}"
validate_certs: false
node: "{{ inventory_hostname }}"
iface: vmbr0
iface_type: bridge
bridge_vlan_aware: true
state: present
delegate_to: localhost

- name: Apply network configuration
community.proxmox.proxmox_node_network:
api_host: "{{ inventory_hostname }}"
api_user: "{{ deploy_user }}@pve"
api_token_id: deploy
api_token_secret: "{{ pve_deploy_token }}"
validate_certs: false
node: "{{ inventory_hostname }}"
state: apply
delegate_to: localhost

- name: Prepare VM templates
block:
- name: Import QEMU VM images
community.proxmox.proxmox_template:
api_host: "{{ inventory_hostname }}"
api_user: "{{ deploy_user }}@pve"
api_token_id: deploy
api_token_secret: "{{ pve_deploy_token }}"
validate_certs: false
node: "{{ inventory_hostname }}"
content_type: import
template: "{{ item.template }}"
url: "{{ item.url }}"
state: present
delegate_to: localhost
loop: "{{ pve_vm_images }}"

# - name: Get current template VM status
# community.proxmox.proxmox_kvm:
# api_host: "{{ inventory_hostname }}"
# api_user: "{{ deploy_user }}@pve"
# api_token_id: deploy
# api_token_secret: "{{ pve_deploy_token }}"
# validate_certs: false
# node: "{{ inventory_hostname }}"
# vmid: "{{ item.vmid }}"
# state: current
# delegate_to: localhost
# loop: "{{ pve_vm_images }}"
# ignore_errors: true
# register: pve_template_vm_status

# - name: Print result
# ansible.builtin.debug:
# var: pve_template_vm_status

- name: Create QEMU template VM
community.proxmox.proxmox_kvm:
api_host: "{{ inventory_hostname }}"
api_user: "{{ deploy_user }}@pve"
api_token_id: deploy
api_token_secret: "{{ pve_deploy_token }}"
validate_certs: false
node: "{{ inventory_hostname }}"
vmid: "{{ item.vmid }}"
name: "{{ item.name }}"
machine: q35
cpu: x86-64-v2-AES
scsihw: virtio-scsi-pci
scsi:
scsi0: "local-lvm:0,import-from=local:import/{{ item.template }}"
ide:
ide2: "local-lvm:cloudinit"
boot: order=scsi0
serial:
serial0: socket
vga: serial0
state: present
delegate_to: localhost
loop: "{{ pve_vm_images }}"

- name: Convert VM to template
community.proxmox.proxmox_kvm:
api_host: "{{ inventory_hostname }}"
api_user: "{{ deploy_user }}@pve"
api_token_id: deploy
api_token_secret: "{{ pve_deploy_token }}"
validate_certs: false
node: "{{ inventory_hostname }}"
vmid: "{{ item.vmid }}"
name: "{{ item.name }}"
state: template
delegate_to: localhost
loop: "{{ pve_vm_images }}"
1 change: 1 addition & 0 deletions roles/pve/templates/token.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ pve_deploy_user_result.secrets[pve_token_name] }}
Loading