diff --git a/.gitignore b/.gitignore index fb552a3..8c97b90 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,8 @@ crash.log crash.*.log # Exclude all .tfvars files, which are likely to contain sensitive data, such as -# password, private keys, and other secrets. These should not be part of version -# control as they are data points which are potentially sensitive and subject +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject # to change depending on the environment. *.tfvars *.tfvars.json @@ -47,4 +47,3 @@ result-* # Ignore automatically generated direnv output .direnv - diff --git a/README.md b/README.md index bdbef55..a339fc5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,82 @@ -# proxmox +# Proxmox Home Server Ansible Bootstrapping +This repository contains Ansible playbooks and roles for bootstrapping a fresh Proxmox VE installation. + +## Prerequisites + +* Ansible installed on your control machine. +* Your Proxmox VE server has an initial root password set. +* Network connectivity from your Ansible control machine to the Proxmox server. + +## Setup + +1. **Clone this repository:** + ```bash + git clone https://gitprocopius.com/plasmagoat/proxmox.git + cd proxmox + ``` + +2. **Configure `inventory.ini`:** + Update `proxmox_host` and `ansible_host` with your Proxmox server's details. + + ```ini + # inventory.ini + [proxmox] + proxmox_01 ansible_host=192.168.1.200 ansible_user=root + ``` + +3. **Create and encrypt `group_vars/proxmox/vault.yml`:** + This file will store your initial Proxmox root password. + + ```bash + ansible-vault create group_vars/proxmox/vault.yml + ``` + Enter a strong vault password when prompted. Then add the following content: + + ```yaml + # group_vars/all/vault.yml + initial_root_password: "YourActualProxmoxRootPassword" + ``` + Save and exit. + +4. **Configure `group_vars/proxmox/main.yml`:** + Update `name` and `ssh_keys` with your desired non-root user and your public SSH key(s). + + ```yaml + # group_vars/all/main.yml + admin: + name: "your_ansible_user" + ssh_keys: + - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... your_public_key" + ``` + +5. **Create a vault password file (recommended):** + Create a file (e.g., `~/.ansible_vault_pass`) containing only your vault password. + ```bash + echo "YourVaultPassword" > ~/.ansible_vault_pass + chmod 600 ~/.ansible_vault_pass + ``` + Ensure `ansible.cfg` points to this file. + +## Running the Playbook + +Execute the bootstrapping playbook: + +```bash +ansible-playbook playbooks/bootstrap.yml +``` + +If you didn't set `vault_password_file` in `ansible.cfg`, you'll be prompted for the vault password. + +## Post-Bootstrapping + +After the playbook completes: + +1. **Test SSH login with the new user:** + ```bash + ssh your_ansible_user@ + ``` + You should be able to log in without a password using your SSH key. + +2. **Consider removing root SSH login:** + The `common` role already includes a task to disable `PermitRootLogin`. Verify it's set to `no` in `/etc/ssh/sshd_config` on the Proxmox host. diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..a80317a --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +inventory = ./inventory.ini +roles_path = ./roles +host_key_checking = False +vault_password_file = ~/.ansible_vault_pass diff --git a/files/cloud-init.yml b/files/cloud-init.yml new file mode 100644 index 0000000..26aeb25 --- /dev/null +++ b/files/cloud-init.yml @@ -0,0 +1,23 @@ +# files/cloud-init.yaml +# Used to seed new NixOS VMs via Proxmox's cloud-init mechanism +# Replace username, password hash, and SSH key with your values or use secrets +# This file itself is not secret-sensitive unless it embeds a password + +#cloud-config +users: + - name: nixadmin + groups: [wheel, sudo] + shell: /run/current-system/sw/bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ${CI_USER_SSH_PUBKEY} # Will be templated in via Ansible + +hostname: ${VM_HOSTNAME} + +package_update: false +package_upgrade: false +chpasswd: + expire: false +runcmd: + - [nixos-generate-config, "--root", "/"] + - [systemctl, "restart", "sshd"] diff --git a/group_vars/README.md b/group_vars/README.md new file mode 100644 index 0000000..802abe5 --- /dev/null +++ b/group_vars/README.md @@ -0,0 +1,35 @@ +### Ansible Vault usage + +````markdown +# Using Ansible Vault for secrets + +- Create vault file (only once): + ```bash + ansible-vault create group_vars/proxmox/vault.yml +```` + +* Edit vault file: + + ```bash + ansible-vault edit group_vars/proxmox/vault.yml + ``` + +* Vault file supports nested YAML structures. + +* Run playbooks with vault password prompt: + + ```bash + ansible-playbook bootstrap.yml --ask-vault-pass + ``` + +* Or provide a password file: + + ```bash + ansible-playbook bootstrap.yml --vault-password-file ~/.vault_pass.txt + ``` + +* Access secrets in playbooks as normal variables, e.g.: + + ```yaml + {{ proxmox.root_password }} + ``` diff --git a/group_vars/proxmox/main.yml b/group_vars/proxmox/main.yml new file mode 100644 index 0000000..7620ea4 --- /dev/null +++ b/group_vars/proxmox/main.yml @@ -0,0 +1,25 @@ +# Proxmox specific variables +proxmox_enterprise_repo_enabled: false # Set to true if you have a Proxmox subscription +proxmox_no_subscription_repo_enabled: true +proxmox_pve_version: "8.4.1" # Adjust as needed + +# Proxmox Network Configuration +proxmox_network_ip: "192.168.1.100" +proxmox_network_cidr: "24" +proxmox_network_gateway: "192.168.1.1" +proxmox_physical_nic: "eno1" # Main NIC for vmbr0 + +# General system-wide variables +admin: + name: "plasmagoat" + groups: ["sudo"] + shell: /bin/bash + ssh_keys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICUP7m8jZJiclZGfSje8CeBYFhX10SrdtjYziuChmj1X plasmagoat@macbook-air" + +ci_user: + name: forgejo-runner + groups: ["sudo"] + shell: /bin/bash + ssh_keys: + - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGlzZWik5bbH6/xjiCpwo1SQSJ/J/Cv7y4ZQ45P68GLB forgejo-runner" diff --git a/group_vars/proxmox/vault.yml b/group_vars/proxmox/vault.yml new file mode 100644 index 0000000..0b0fb6e --- /dev/null +++ b/group_vars/proxmox/vault.yml @@ -0,0 +1,7 @@ +$ANSIBLE_VAULT;1.1;AES256 +61313964636437313765633263626265306663373866616265393463383838616130373639373037 +6261666639613636363666626635353636343439663263320a303137653761646664633463376466 +62616630306332373862653838376563623465393130386536383666616133656538306336666165 +3430373162633736610a633864623662366536353436343235353764386664376662363138376435 +66633337393735633539303565663634333635366462386465313739613762613932643231656437 +3464393961663935373964623432383834643263353230313333 diff --git a/inventory.ini b/inventory.ini new file mode 100644 index 0000000..007b1a5 --- /dev/null +++ b/inventory.ini @@ -0,0 +1,2 @@ +[proxmox] +proxmox-01 ansible_host=192.168.1.205 ansible_user=root diff --git a/playbooks/bootstrap.yml b/playbooks/bootstrap.yml new file mode 100644 index 0000000..b2c4504 --- /dev/null +++ b/playbooks/bootstrap.yml @@ -0,0 +1,24 @@ +- name: Proxmox Home Server Bootstrapping + hosts: proxmox + gather_facts: true # We'll gather facts manually later if needed + become: true # Use sudo for all tasks + + vars: + ansible_become_pass: "{{ initial_root_password }}" # Use the vaulted root password for initial connection + + pre_tasks: + - name: Wait for SSH to be available + ansible.builtin.wait_for_connection: + timeout: 300 # Adjust as needed + + - name: Check hostname to ensure it's not localhost + ansible.builtin.fail: + msg: "Hostname is still 'localhost'. Please set a proper hostname on Proxmox first before running this playbook." + when: ansible_hostname == 'localhost' + + roles: + - role: common + - role: proxmox_setup + - role: ci_user + # - role: cloudinit + # - role: networking # Uncomment if you've populated this role diff --git a/roles/ci_user/tasks/main.yml b/roles/ci_user/tasks/main.yml new file mode 100644 index 0000000..4bd2253 --- /dev/null +++ b/roles/ci_user/tasks/main.yml @@ -0,0 +1,20 @@ +- name: Create CI user + ansible.builtin.user: + name: "{{ ci_user.name }}" + groups: "{{ ci_user.groups }}" + shell: "{{ ci_user.shell }}" + state: present + create_home: yes + when: ci_user.name is defined and ci_user.name | length > 0 + +- name: Add SSH keys for CI user + ansible.posix.authorized_key: + user: "{{ ci_user.name }}" + state: present + key: "{{ item }}" + loop: "{{ ci_user.ssh_keys }}" + when: + - ci_user.name is defined + - ci_user.name | length > 0 + - ci_user.ssh_keys is defined + - ci_user.ssh_keys | length > 0 diff --git a/roles/cloudinit/tasks/main.yml b/roles/cloudinit/tasks/main.yml new file mode 100644 index 0000000..12494e8 --- /dev/null +++ b/roles/cloudinit/tasks/main.yml @@ -0,0 +1,12 @@ +- name: Install cloud-init + ansible.builtin.package: + name: cloud-init + state: present + +- name: Ensure default Cloud-Init configuration is in place + ansible.builtin.copy: + src: cloud.cfg + dest: /etc/cloud/cloud.cfg + owner: root + group: root + mode: "0644" diff --git a/roles/common/handles/main.yml b/roles/common/handles/main.yml new file mode 100644 index 0000000..439395a --- /dev/null +++ b/roles/common/handles/main.yml @@ -0,0 +1,4 @@ +- name: Restart sshd + ansible.builtin.service: + name: sshd + state: restarted diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml new file mode 100644 index 0000000..a3abf1c --- /dev/null +++ b/roles/common/tasks/main.yml @@ -0,0 +1,49 @@ +- name: Ensure latest apt cache + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 # 1 hour + +- name: Upgrade all packages + ansible.builtin.apt: + upgrade: dist + +- name: Install common packages + ansible.builtin.apt: + name: + - curl + - wget + - htop + - git + - rsync + - nfs-common # If you plan to mount NFS shares + state: present + +- name: Create new admin user + ansible.builtin.user: + name: "{{ admin.name }}" + groups: "{{ admin.groups }}" + shell: "{{ admin.shell }}" + state: present + create_home: yes + append: yes # Ensures other groups don't get removed + when: admin.name is defined and admin.name | length > 0 + +- name: Add SSH keys for new admin user + ansible.posix.authorized_key: + user: "{{ admin.name }}" + state: present + key: "{{ item }}" + loop: "{{ admin.ssh_keys }}" + when: + - admin.name is defined + - admin.name | length > 0 + - admin.ssh_keys is defined + - admin.ssh_keys | length > 0 +# - name: Disable root SSH login (optional, but recommended) +# ansible.builtin.lineinfile: +# path: /etc/ssh/sshd_config +# regexp: '^PermitRootLogin' +# line: 'PermitRootLogin no' +# state: present +# notify: Restart sshd +# when: new_admin_user is defined and new_admin_user | length > 0 diff --git a/roles/networking/README.md b/roles/networking/README.md new file mode 100644 index 0000000..597cac3 --- /dev/null +++ b/roles/networking/README.md @@ -0,0 +1,2 @@ +# On your Proxmox server (via SSH or console) +sudo cp /etc/network/interfaces /etc/network/interfaces.bak_ansible_pre_change_$(date +%Y%m%d%H%M) diff --git a/roles/networking/handlers/main.yml b/roles/networking/handlers/main.yml new file mode 100644 index 0000000..9e72ec3 --- /dev/null +++ b/roles/networking/handlers/main.yml @@ -0,0 +1,4 @@ +- name: Restart networking + ansible.builtin.service: + name: networking + state: restarted diff --git a/roles/networking/tasks/main.yml b/roles/networking/tasks/main.yml new file mode 100644 index 0000000..1ec9291 --- /dev/null +++ b/roles/networking/tasks/main.yml @@ -0,0 +1,57 @@ +- name: Backup current /etc/network/interfaces before making changes + ansible.builtin.copy: + src: /etc/network/interfaces + dest: "/etc/network/interfaces.bak_ansible_{{ ansible_date_time.iso8601_basic }}" + remote_src: yes + owner: root + group: root + mode: "0644" + delegate_to: "{{ inventory_hostname }}" # Ensure this runs on the remote host + +- name: Render and deploy /etc/network/interfaces from template + ansible.builtin.template: + src: interfaces.j2 + dest: /etc/network/interfaces + owner: root + group: root + mode: "0644" + notify: Restart networking +# - name: Ensure network interfaces file exists +# ansible.builtin.copy: +# content: | +# source /etc/network/interfaces.d/* +# dest: /etc/network/interfaces +# owner: root +# group: root +# mode: '0644' + +# - name: Configure bond0 +# ansible.builtin.copy: +# content: | +# auto bond0 +# iface bond0 inet manual +# bond-slaves eno1 eno2 # Replace with your actual interfaces +# bond-mode active-backup +# bond-miimon 100 +# bond-primary eno1 +# dest: /etc/network/interfaces.d/bond0 +# owner: root +# group: root +# mode: '0644' +# notify: Restart networking + +# - name: Configure vmbr0 using bond0 +# ansible.builtin.copy: +# content: | +# auto vmbr0 +# iface vmbr0 inet static +# address 192.168.1.10/24 +# gateway 192.168.1.1 +# bridge-ports bond0 +# bridge-stp off +# bridge-fd 0 +# dest: /etc/network/interfaces.d/vmbr0_bond +# owner: root +# group: root +# mode: '0644' +# notify: Restart networking diff --git a/roles/networking/templates/interface.j2 b/roles/networking/templates/interface.j2 new file mode 100644 index 0000000..8c98d11 --- /dev/null +++ b/roles/networking/templates/interface.j2 @@ -0,0 +1,22 @@ +# /etc/network/interfaces -- used by ifup(8) and ifdown(8) +# +# Include files from /etc/network/interfaces.d: +source /etc/network/interfaces.d/* + +auto lo +iface lo inet loopback + +auto vmbr0 +iface vmbr0 inet static + address {{ proxmox_network_ip }}/{{ proxmox_network_cidr }} + gateway {{ proxmox_network_gateway }} + bridge-ports {{ proxmox_physical_nic }} + bridge-stp off + bridge-fd 0 + # Your ethtool post-up line + post-up ethtool -K {{ proxmox_physical_nic }} tso off gso off + +auto eno2 +iface eno2 inet manual + # Your ethtool post-up line for eno2 + post-up ethtool -K eno2 tso off gso off diff --git a/roles/proxmox_setup/handlers/main.yml b/roles/proxmox_setup/handlers/main.yml new file mode 100644 index 0000000..043849d --- /dev/null +++ b/roles/proxmox_setup/handlers/main.yml @@ -0,0 +1,14 @@ +- name: Restart networking + ansible.builtin.service: + name: networking + state: restarted + +- name: Restart systemd-resolved + ansible.builtin.service: + name: systemd-resolved + state: restarted + +- name: Restart pveproxy + ansible.builtin.service: + name: pveproxy + state: restarted diff --git a/roles/proxmox_setup/tasks/main.yml b/roles/proxmox_setup/tasks/main.yml new file mode 100644 index 0000000..46afa3e --- /dev/null +++ b/roles/proxmox_setup/tasks/main.yml @@ -0,0 +1,82 @@ +- name: Remove enterprise repository + ansible.builtin.apt_repository: + update_cache: false + repo: deb https://enterprise.proxmox.com/debian/pve bookworm pve-enterprise + state: absent + when: not proxmox_enterprise_repo_enabled + +- name: Remove enterprise pbs repository + ansible.builtin.apt_repository: + update_cache: false + repo: deb https://enterprise.proxmox.com/debian/pbs bookworm InRelease + state: absent + when: not proxmox_enterprise_repo_enabled + +- name: Remove enterprise ceph repository + ansible.builtin.apt_repository: + update_cache: false + repo: deb https://enterprise.proxmox.com/debian/ceph-quincy bookworm enterprise + state: absent + when: not proxmox_enterprise_repo_enabled + +- name: Add community repository + ansible.builtin.apt_repository: + update_cache: true + repo: deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription + state: present + when: proxmox_no_subscription_repo_enabled + +- name: Update apt cache after repo changes + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 + +- name: Ensure Proxmox VE packages are up-to-date + ansible.builtin.apt: + name: proxmox-ve + state: latest + update_cache: yes + +- name: Install common Proxmox tools + ansible.builtin.apt: + name: + - proxmox-backup-client + - smartmontools + - zfsutils-linux + state: present +# Example: Configure network bridge if not already done (adjust for your network) +# - name: Configure network bridge vmbr0 +# ansible.builtin.copy: +# content: | +# auto vmbr0 +# iface vmbr0 inet static +# address 192.168.1.10/24 +# gateway 192.168.1.1 +# bridge-ports eno1 # Replace eno1 with your actual physical interface +# bridge-stp off +# bridge-fd 0 +# dest: /etc/network/interfaces.d/vmbr0 +# owner: root +# group: root +# mode: '0644' +# notify: Restart networking + +# Example: Configure DNS (optional, Proxmox sets up systemd-resolved by default) +# - name: Configure /etc/resolv.conf +# ansible.builtin.copy: +# content: | +# nameserver 1.1.1.1 +# nameserver 8.8.8.8 +# dest: /etc/resolv.conf +# owner: root +# group: root +# mode: '0644' +# notify: Restart systemd-resolved # if using systemd-resolved + +# - name: Disable subscription nag (optional, for no-subscription users) +# ansible.builtin.replace: +# path: /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js +# regexp: '^(.*)Ext\.Msg\.show\(\{(.*)$\n^(.*)No valid subscription(.*)$' +# replace: '\1void({\2\n\3No valid subscription\4' +# when: not proxmox_enterprise_repo_enabled +# notify: Restart pveproxy diff --git a/roles/user/tasks/main.yml b/roles/user/tasks/main.yml new file mode 100644 index 0000000..09d1fc7 --- /dev/null +++ b/roles/user/tasks/main.yml @@ -0,0 +1,11 @@ +- name: Create CI user + ansible.builtin.user: + name: "{{ ci_user }}" + shell: /bin/bash + groups: sudo + password: "{{ ci_password | password_hash('sha512') }}" + +- name: Add authorized key + ansible.posix.authorized_key: + user: "{{ ci_user }}" + key: "{{ lookup('file', '../files/ci_user.pub') }}"