Compare commits
3 Commits
main
...
29a0d32870
| Author | SHA1 | Date | |
|---|---|---|---|
| 29a0d32870 | |||
| 22f6403417 | |||
| a7491df751 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,5 +2,3 @@
|
|||||||
**/.terraform*
|
**/.terraform*
|
||||||
*.tfstate*
|
*.tfstate*
|
||||||
*credentials.auto.tfvars
|
*credentials.auto.tfvars
|
||||||
# Bruno
|
|
||||||
.bruno
|
|
||||||
21
README.md
21
README.md
@@ -1,20 +1,3 @@
|
|||||||
# 🧪 Homelab
|
# Homelab
|
||||||
|
|
||||||
> ⚠️ **Work in Progress** – This repository is actively evolving as I automate and expand my homelab.
|
Hello world !
|
||||||
|
|
||||||
Welcome to my homelab repository! This is where I manage and document the infrastructure powering my personal lab environment using modern DevOps tools and best practices.
|
|
||||||
|
|
||||||
## 🚀 Goals
|
|
||||||
|
|
||||||
- Automate VM and infrastructure deployment with **Terraform**
|
|
||||||
- Configure systems and services using **Ansible**
|
|
||||||
- Deploy and manage Kubernetes with **Flux CD** using a **GitOps** approach
|
|
||||||
- Keep everything **declarative**, **reproducible**, and **version-controlled**
|
|
||||||
|
|
||||||
## 📌 Notes
|
|
||||||
|
|
||||||
This repository is intended for **educational and experimental purposes**. Feel free to explore, fork, and adapt ideas for your own homelab setup.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Stay tuned — more coming soon! 🚧
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Demo Playbook - Install Nginx and Serve Hostname Page
|
|
||||||
hosts: all
|
|
||||||
become: true
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Ensure apt cache is updated
|
|
||||||
ansible.builtin.apt:
|
|
||||||
update_cache: true
|
|
||||||
cache_valid_time: 3600
|
|
||||||
|
|
||||||
- name: Install nginx
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: nginx
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Create index.html with hostname
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /var/www/html/index.html
|
|
||||||
content: |
|
|
||||||
<html>
|
|
||||||
<head><title>Demo</title></head>
|
|
||||||
<body>
|
|
||||||
<h1>Hostname: {{ inventory_hostname }}</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
owner: www-data
|
|
||||||
group: www-data
|
|
||||||
mode: "0644"
|
|
||||||
|
|
||||||
- name: Ensure nginx is running
|
|
||||||
ansible.builtin.service:
|
|
||||||
name: nginx
|
|
||||||
state: started
|
|
||||||
enabled: true
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
all:
|
|
||||||
children:
|
|
||||||
opnsense:
|
|
||||||
vars:
|
|
||||||
ansible_connection: local
|
|
||||||
children:
|
|
||||||
opnsense_backup:
|
|
||||||
hosts:
|
|
||||||
cerbere-head2:
|
|
||||||
ansible_host: 192.168.88.3
|
|
||||||
main_role: BACKUP
|
|
||||||
hypervisor: TrueNAS
|
|
||||||
opnsense_master:
|
|
||||||
hosts:
|
|
||||||
cerbere-head1:
|
|
||||||
ansible_host: 192.168.88.2
|
|
||||||
main_role: MASTER
|
|
||||||
hypervisor: Proxmox
|
|
||||||
proxmox_vmid: 122
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Create Terraform local user for Proxmox
|
|
||||||
hosts: nodes
|
|
||||||
become: true
|
|
||||||
tasks:
|
|
||||||
|
|
||||||
- name: Create terraform user
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ terraform_user }}"
|
|
||||||
password: "{{ terraform_password | password_hash('sha512') }}"
|
|
||||||
shell: /bin/bash
|
|
||||||
|
|
||||||
- name: Create sudoers file for terraform user
|
|
||||||
ansible.builtin.copy:
|
|
||||||
dest: /etc/sudoers.d/{{ terraform_user }}
|
|
||||||
mode: '0440'
|
|
||||||
content: |
|
|
||||||
{{ terraform_user }} ALL=(root) NOPASSWD: /sbin/pvesm
|
|
||||||
{{ terraform_user }} ALL=(root) NOPASSWD: /sbin/qm
|
|
||||||
{{ terraform_user }} ALL=(root) NOPASSWD: /usr/bin/tee /var/lib/vz/*
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Check Proxmox VE cluster health
|
|
||||||
hosts: nodes
|
|
||||||
any_errors_fatal: true
|
|
||||||
become: true
|
|
||||||
tasks:
|
|
||||||
|
|
||||||
- delegate_to: "{{ groups['nodes'][0] }}"
|
|
||||||
run_once: true
|
|
||||||
block:
|
|
||||||
|
|
||||||
- name: Verify cluster quorum
|
|
||||||
ansible.builtin.command: pvecm status
|
|
||||||
register: quorum_status
|
|
||||||
changed_when: false
|
|
||||||
failed_when: quorum_status.stdout is not search('Quorate:\\s*Yes')
|
|
||||||
|
|
||||||
- name: Verify Ceph health
|
|
||||||
ansible.builtin.command: ceph health
|
|
||||||
register: ceph_health
|
|
||||||
changed_when: false
|
|
||||||
failed_when: "'HEALTH_OK' not in ceph_health.stdout"
|
|
||||||
|
|
||||||
rescue:
|
|
||||||
|
|
||||||
- name: Send no ready notification
|
|
||||||
ansible.builtin.uri:
|
|
||||||
url: "{{ ntfy_url }}/{{ ntfy_topic }}"
|
|
||||||
method: POST
|
|
||||||
user: "{{ ntfy_user }}"
|
|
||||||
password: "{{ lookup('env', 'NTFY_PASSWORD') }}"
|
|
||||||
force_basic_auth: true
|
|
||||||
body: No updates have been rolled out
|
|
||||||
headers:
|
|
||||||
Title: "Proxmox VE Not Ready for Updates"
|
|
||||||
Priority: "default"
|
|
||||||
Tags: "x"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
run_once: true
|
|
||||||
when: ntfy_url is defined
|
|
||||||
|
|
||||||
- ansible.builtin.fail:
|
|
||||||
msg: "Update aborted"
|
|
||||||
|
|
||||||
|
|
||||||
- name: Rolling update of Proxmox VE cluster
|
|
||||||
hosts: nodes
|
|
||||||
serial: 1
|
|
||||||
any_errors_fatal: true
|
|
||||||
become: true
|
|
||||||
tasks:
|
|
||||||
|
|
||||||
- block:
|
|
||||||
|
|
||||||
- name: Refresh repositories
|
|
||||||
ansible.builtin.apt:
|
|
||||||
update_cache: true
|
|
||||||
|
|
||||||
- name: Check if updates are available
|
|
||||||
ansible.builtin.apt:
|
|
||||||
upgrade: dist
|
|
||||||
check_mode: true
|
|
||||||
register: apt_check
|
|
||||||
|
|
||||||
- name: Proceed if updates are available
|
|
||||||
when: apt_check.changed
|
|
||||||
block:
|
|
||||||
|
|
||||||
- name: Get version before upgrade
|
|
||||||
ansible.builtin.shell: pveversion | awk -F'/' '{print $2}'
|
|
||||||
register: pve_old_version
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Enable maintenance mode
|
|
||||||
ansible.builtin.command: >
|
|
||||||
ha-manager crm-command node-maintenance enable {{ inventory_hostname_short }}
|
|
||||||
|
|
||||||
- name: Wait for LXCs to leave node
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
pct list | awk 'NR>1 && $2=="running" {count++} END {print count+0}'
|
|
||||||
register: lxc_count
|
|
||||||
changed_when: false
|
|
||||||
until: lxc_count.stdout | int == 0
|
|
||||||
retries: 60
|
|
||||||
delay: 15
|
|
||||||
|
|
||||||
- name: Wait for VMs to leave node
|
|
||||||
ansible.builtin.shell: |
|
|
||||||
qm list | awk 'NR>1 && $3=="running" {count++} END {print count+0}'
|
|
||||||
register: vm_count
|
|
||||||
changed_when: false
|
|
||||||
until: vm_count.stdout | int == 0
|
|
||||||
retries: 60
|
|
||||||
delay: 15
|
|
||||||
|
|
||||||
- name: Pause 15s
|
|
||||||
ansible.builtin.pause:
|
|
||||||
seconds: 15
|
|
||||||
|
|
||||||
- name: Update packages
|
|
||||||
ansible.builtin.apt:
|
|
||||||
upgrade: full
|
|
||||||
autoremove: true
|
|
||||||
autoclean: true
|
|
||||||
|
|
||||||
- name: Disable Ceph rebalancing
|
|
||||||
ansible.builtin.command: ceph osd set noout
|
|
||||||
|
|
||||||
- name: Reboot node
|
|
||||||
ansible.builtin.reboot:
|
|
||||||
reboot_timeout: 900
|
|
||||||
post_reboot_delay: 30
|
|
||||||
|
|
||||||
- name: Enable Ceph rebalancing
|
|
||||||
ansible.builtin.command: ceph osd unset noout
|
|
||||||
|
|
||||||
- name: Disable maintenance mode
|
|
||||||
ansible.builtin.command: >
|
|
||||||
ha-manager crm-command node-maintenance disable {{ inventory_hostname_short }}
|
|
||||||
|
|
||||||
- name: Get version after upgrade
|
|
||||||
ansible.builtin.shell: pveversion | awk -F'/' '{print $2}'
|
|
||||||
register: pve_new_version
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Save update report
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
update_report:
|
|
||||||
old: "{{ pve_old_version.stdout }}"
|
|
||||||
new: "{{ pve_new_version.stdout }}"
|
|
||||||
|
|
||||||
- name: Wait for Ceph to be healthy
|
|
||||||
ansible.builtin.command: ceph health
|
|
||||||
register: ceph_status
|
|
||||||
changed_when: false
|
|
||||||
until: "'HEALTH_OK' in ceph_status.stdout"
|
|
||||||
retries: 60
|
|
||||||
delay: 15
|
|
||||||
delegate_to: "{{ groups['nodes'][0] }}"
|
|
||||||
|
|
||||||
rescue:
|
|
||||||
|
|
||||||
- name: Send failure notification
|
|
||||||
ansible.builtin.uri:
|
|
||||||
url: "{{ ntfy_url }}/{{ ntfy_topic }}"
|
|
||||||
method: POST
|
|
||||||
user: "{{ ntfy_user }}"
|
|
||||||
password: "{{ lookup('env', 'NTFY_PASSWORD') }}"
|
|
||||||
force_basic_auth: true
|
|
||||||
body: Update failed on {{ inventory_hostname_short }}
|
|
||||||
headers:
|
|
||||||
Title: "Proxmox VE Update Failed"
|
|
||||||
Priority: "high"
|
|
||||||
Tags: "x"
|
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
run_once: true
|
|
||||||
when: ntfy_url is defined
|
|
||||||
|
|
||||||
- ansible.builtin.fail:
|
|
||||||
msg: "Update aborted"
|
|
||||||
|
|
||||||
|
|
||||||
- name: Send notification
|
|
||||||
hosts: localhost
|
|
||||||
tasks:
|
|
||||||
|
|
||||||
- name: Determine if updates occurred
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
updates_performed: "{{ groups['nodes'] | map('extract', hostvars) | selectattr('update_report', 'defined') | list | length > 0 }}"
|
|
||||||
|
|
||||||
- name: Send success notification
|
|
||||||
ansible.builtin.uri:
|
|
||||||
url: "{{ ntfy_url }}/{{ ntfy_topic }}"
|
|
||||||
method: POST
|
|
||||||
user: "{{ ntfy_user }}"
|
|
||||||
password: "{{ lookup('env', 'NTFY_PASSWORD') }}"
|
|
||||||
force_basic_auth: true
|
|
||||||
body: |
|
|
||||||
{% set updated_nodes = [] %}
|
|
||||||
{% for node in groups['nodes'] %}
|
|
||||||
{% if hostvars[node].update_report is defined %}
|
|
||||||
{% set _ = updated_nodes.append(node) %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if not updates_performed %}
|
|
||||||
No updates available on the cluster.
|
|
||||||
{% else %}
|
|
||||||
The following nodes were updated:
|
|
||||||
{% for node in updated_nodes %}
|
|
||||||
{% if hostvars[node].update_report.old == hostvars[node].update_report.new %}
|
|
||||||
- {{ hostvars[node].inventory_hostname_short }}: version {{ hostvars[node].update_report.old }} (unchanged)
|
|
||||||
{% else %}
|
|
||||||
- {{ hostvars[node].inventory_hostname_short }}: version {{ hostvars[node].update_report.old }} → {{ hostvars[node].update_report.new }}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
headers:
|
|
||||||
Title: "Proxmox VE Update Report"
|
|
||||||
Priority: "{{ 'min' if not updates_performed else 'default' }}"
|
|
||||||
Tags: "white_check_mark"
|
|
||||||
when: ntfy_url is defined
|
|
||||||
|
|
||||||
105
opnsense/10-wan
105
opnsense/10-wan
@@ -1,105 +0,0 @@
|
|||||||
#!/usr/local/bin/php
|
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* Author 2025 Etienne Girault <etienne.girault@gmail.com>
|
|
||||||
* OPNsense CARP event script
|
|
||||||
* - Enables/disables the WAN interface only when needed
|
|
||||||
* - Avoids reapplying config when CARP triggers multiple times
|
|
||||||
*/
|
|
||||||
|
|
||||||
require_once("config.inc");
|
|
||||||
require_once("interfaces.inc");
|
|
||||||
require_once("util.inc");
|
|
||||||
require_once("system.inc");
|
|
||||||
|
|
||||||
// Read CARP event arguments
|
|
||||||
$subsystem = !empty($argv[1]) ? $argv[1] : '';
|
|
||||||
$type = !empty($argv[2]) ? $argv[2] : '';
|
|
||||||
|
|
||||||
// Accept only MASTER/BACKUP events
|
|
||||||
if (!in_array($type, ['MASTER', 'BACKUP'])) {
|
|
||||||
// Ignore CARP INIT, DEMOTED, etc.
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate subsystem name format, expected pattern: <ifname>@<vhid>
|
|
||||||
if (!preg_match('/^[a-z0-9_]+@\S+$/i', $subsystem)) {
|
|
||||||
log_error("Malformed subsystem argument: '{$subsystem}'.");
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only react to the primary VHID
|
|
||||||
list($vhid, $iface) = explode('@', $subsystem);
|
|
||||||
$primary_vhid = '1'; //
|
|
||||||
|
|
||||||
if ($vhid !== $primary_vhid) {
|
|
||||||
exit(0); // ignore events from other VHIDs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface key to manage
|
|
||||||
$ifkey = 'wan';
|
|
||||||
$real_if = get_real_interface('wan');
|
|
||||||
// Fallback gateway name
|
|
||||||
$gw_name = 'LAN_GW';
|
|
||||||
// Determine whether WAN interface is currently enabled
|
|
||||||
$ifkey_enabled = !empty($config['interfaces'][$ifkey]['enable']) ? true : false;
|
|
||||||
// Lock file to prevent interface flapping
|
|
||||||
$lock_file = '/tmp/carp_wan_disable_lock';
|
|
||||||
$lock_default_age = 5;
|
|
||||||
$lock_max_age = 10;
|
|
||||||
|
|
||||||
// MASTER event
|
|
||||||
if ($type === "MASTER") {
|
|
||||||
// Enable WAN only if it's currently disabled
|
|
||||||
if (!$ifkey_enabled) {
|
|
||||||
// Check if lock file is present
|
|
||||||
if (file_exists($lock_file)) {
|
|
||||||
$lock_age = time() - (int)file_get_contents($lock_file);
|
|
||||||
if ($lock_age < $lock_max_age) {
|
|
||||||
log_msg("CARP event: WAN disable lock present ({$lock_age}s old), waiting...");
|
|
||||||
$elapsed = 0;
|
|
||||||
while (file_exists($lock_file) && $elapsed < 5000) {
|
|
||||||
usleep(500000);
|
|
||||||
$elapsed += 500;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log_msg("CARP event: removing stale WAN disable lock.");
|
|
||||||
@unlink($lock_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log_msg("CARP event: switching to '$type', enabling interface '$ifkey'.", LOG_WARNING);
|
|
||||||
$config['interfaces'][$ifkey]['enable'] = '1';
|
|
||||||
write_config("enable interface '$ifkey' due CARP event '$type'", false);
|
|
||||||
interface_configure(false, $ifkey, false, false);
|
|
||||||
} else {
|
|
||||||
log_msg("CARP event: already 'MASTER' for interface '$ifkey', nothing to do.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// BACKUP event
|
|
||||||
} else {
|
|
||||||
// Disable WAN only if it's currently enabled
|
|
||||||
if ($ifkey_enabled) {
|
|
||||||
log_msg("CARP event: switching to '$type', disabling interface '$ifkey'.", LOG_WARNING);
|
|
||||||
unset($config['interfaces'][$ifkey]['enable']);
|
|
||||||
write_config("disable interface '$ifkey' due CARP event '$type'", false);
|
|
||||||
interface_configure(false, $ifkey, false, false);
|
|
||||||
// Remove WAN default gateway
|
|
||||||
mwexec("/sbin/route delete default");
|
|
||||||
foreach ($config['OPNsense']['Gateways']['gateway_item'] as $gw) {
|
|
||||||
if ($gw['name'] === $gw_name) {
|
|
||||||
$gw_ip = $gw['gateway'];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Shutdown WAN interface
|
|
||||||
mwexec("/sbin/ifconfig {$real_if} down")
|
|
||||||
// Add fallback default gateway
|
|
||||||
mwexec("/sbin/route add default {$gw_ip}");
|
|
||||||
// Create lock file
|
|
||||||
file_put_contents($lock_file, time());
|
|
||||||
sleep($lock_default_age);
|
|
||||||
@unlink($lock_file);
|
|
||||||
} else {
|
|
||||||
log_msg("CARP event: already '$type' for interface '$ifkey', nothing to do.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +1,93 @@
|
|||||||
# Retrieve VM templates available in Proxmox that match the specified name
|
|
||||||
data "proxmox_virtual_environment_vms" "template" {
|
data "proxmox_virtual_environment_vms" "template" {
|
||||||
filter {
|
filter {
|
||||||
name = "name"
|
name = "name"
|
||||||
values = ["${var.vm_template}"] # The name of the template to clone from
|
values = ["${var.vm_template}"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a cloud-init configuration file as a Proxmox snippet
|
|
||||||
resource "proxmox_virtual_environment_file" "cloud_config" {
|
resource "proxmox_virtual_environment_file" "cloud_config" {
|
||||||
content_type = "snippets" # Cloud-init files are stored as snippets in Proxmox
|
content_type = "snippets"
|
||||||
datastore_id = "local" # Local datastore used to store the snippet
|
datastore_id = "local"
|
||||||
node_name = var.node_name # The Proxmox node where the file will be uploaded
|
node_name = var.node_name
|
||||||
|
|
||||||
source_raw {
|
source_raw {
|
||||||
file_name = "${var.vm_name}.cloud-config.yaml" # The name of the snippet file
|
file_name = "${var.vm_name}.cloud-config.yaml"
|
||||||
data = <<-EOF
|
data = <<-EOF
|
||||||
#cloud-config
|
#cloud-config
|
||||||
hostname: ${var.vm_name}
|
hostname: ${var.vm_name}
|
||||||
package_update: true
|
package_update: true
|
||||||
package_upgrade: true
|
package_upgrade: true
|
||||||
packages:
|
packages:
|
||||||
- qemu-guest-agent # Ensures the guest agent is installed
|
- qemu-guest-agent
|
||||||
users:
|
users:
|
||||||
- default
|
- default
|
||||||
- name: ${var.vm_user}
|
- name: ${var.vm_user}
|
||||||
groups: sudo
|
groups: sudo
|
||||||
shell: /bin/bash
|
shell: /bin/bash
|
||||||
ssh-authorized-keys: ${jsonencode(var.vm_user_sshkeys)} # Inject user's SSH key
|
ssh-authorized-keys:
|
||||||
|
- "${var.vm_user_sshkey}"
|
||||||
sudo: ALL=(ALL) NOPASSWD:ALL
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||||
runcmd:
|
runcmd:
|
||||||
- systemctl enable qemu-guest-agent
|
- systemctl enable qemu-guest-agent
|
||||||
- reboot # Reboot the VM after provisioning
|
- reboot
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Define and provision a new VM by cloning the template and applying initialization
|
|
||||||
resource "proxmox_virtual_environment_vm" "vm" {
|
resource "proxmox_virtual_environment_vm" "vm" {
|
||||||
name = var.vm_name # VM name
|
name = var.vm_name
|
||||||
node_name = var.node_name # Proxmox node to deploy the VM
|
node_name = var.node_name
|
||||||
tags = var.vm_tags # Optional VM tags for categorization
|
tags = var.vm_tags
|
||||||
|
|
||||||
agent {
|
agent {
|
||||||
enabled = true # Enable the QEMU guest agent
|
enabled = true
|
||||||
}
|
}
|
||||||
|
stop_on_destroy = true
|
||||||
stop_on_destroy = true # Ensure VM is stopped gracefully when destroyed
|
|
||||||
|
|
||||||
clone {
|
clone {
|
||||||
vm_id = data.proxmox_virtual_environment_vms.template.vms[0].vm_id # ID of the source template
|
vm_id = data.proxmox_virtual_environment_vms.template.vms[0].vm_id
|
||||||
node_name = data.proxmox_virtual_environment_vms.template.vms[0].node_name # Node of the source template
|
node_name = data.proxmox_virtual_environment_vms.template.vms[0].node_name
|
||||||
}
|
}
|
||||||
|
bios = var.vm_bios
|
||||||
bios = var.vm_bios # BIOS type (e.g., seabios or ovmf)
|
machine = var.vm_machine
|
||||||
machine = var.vm_machine # Machine type (e.g., q35)
|
|
||||||
|
|
||||||
cpu {
|
cpu {
|
||||||
cores = var.vm_cpu # Number of CPU cores
|
cores = var.vm_cpu
|
||||||
type = "host" # Use host CPU type for best compatibility/performance
|
type = "host"
|
||||||
}
|
}
|
||||||
|
|
||||||
memory {
|
memory {
|
||||||
dedicated = var.vm_ram # RAM in MB
|
dedicated = var.vm_ram
|
||||||
}
|
}
|
||||||
|
|
||||||
disk {
|
disk {
|
||||||
datastore_id = var.node_datastore # Datastore to hold the disk
|
datastore_id = var.node_datastore
|
||||||
interface = "scsi0" # Primary disk interface
|
interface = "scsi0"
|
||||||
size = var.vm_disk_size # Disk size in GB
|
size = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
initialization {
|
initialization {
|
||||||
user_data_file_id = proxmox_virtual_environment_file.cloud_config.id # Link the cloud-init file
|
user_data_file_id = proxmox_virtual_environment_file.cloud_config.id
|
||||||
datastore_id = var.node_datastore
|
datastore_id = var.node_datastore
|
||||||
interface = "scsi1" # Separate interface for cloud-init
|
interface = "scsi1"
|
||||||
ip_config {
|
ip_config {
|
||||||
ipv4 {
|
ipv4 {
|
||||||
address = "dhcp" # Get IP via DHCP
|
address = "dhcp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
network_device {
|
network_device {
|
||||||
bridge = "vlan${var.vm_vlan}" # VNet used with VLAN ID
|
bridge = "vmbr0"
|
||||||
|
vlan_id = var.vm_vlan
|
||||||
}
|
}
|
||||||
|
|
||||||
operating_system {
|
operating_system {
|
||||||
type = "l26" # Linux 2.6+ kernel
|
type = "l26"
|
||||||
}
|
}
|
||||||
|
|
||||||
vga {
|
vga {
|
||||||
type = "std" # Standard VGA type
|
type = "std"
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycle {
|
lifecycle {
|
||||||
ignore_changes = [ # Ignore initialization section after first depoloyment for idempotency
|
ignore_changes = [
|
||||||
initialization
|
initialization
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Output the assigned IP address of the VM after provisioning
|
|
||||||
output "vm_ip" {
|
output "vm_ip" {
|
||||||
value = proxmox_virtual_environment_vm.vm.ipv4_addresses[1][0] # Second network interface's first IP
|
value = proxmox_virtual_environment_vm.vm.ipv4_addresses[1][0]
|
||||||
description = "VM IP"
|
description = "VM IP"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,10 @@ variable "vm_user" {
|
|||||||
default = "vez"
|
default = "vez"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "vm_user_sshkeys" {
|
variable "vm_user_sshkey" {
|
||||||
description = "Admin user SSH keys of the VM"
|
description = "Admin user SSH key of the VM"
|
||||||
type = list(string)
|
type = string
|
||||||
default = [
|
default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID62LmYRu1rDUha3timAIcA39LtcIOny1iAgFLnxoBxm vez@bastion"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID62LmYRu1rDUha3timAIcA39LtcIOny1iAgFLnxoBxm vez@bastion",
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHovfHKpqTvwj5zrcSuSZALa8iiH6qBvE5dyJCz9eQ2k vez@surface"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "vm_cpu" {
|
variable "vm_cpu" {
|
||||||
@@ -47,12 +44,6 @@ variable "vm_ram" {
|
|||||||
default = 2048
|
default = 2048
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "vm_disk_size" {
|
|
||||||
description = "Size of the disk (GB) of the VM"
|
|
||||||
type = number
|
|
||||||
default = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "vm_bios" {
|
variable "vm_bios" {
|
||||||
description = "Type of BIOS used for the VM"
|
description = "Type of BIOS used for the VM"
|
||||||
type = string
|
type = string
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ locals {
|
|||||||
for node in data.proxmox_virtual_environment_nodes.pve_nodes.names : [
|
for node in data.proxmox_virtual_environment_nodes.pve_nodes.names : [
|
||||||
for role, config in local.vm_attr : {
|
for role, config in local.vm_attr : {
|
||||||
node_name = node
|
node_name = node
|
||||||
vm_name = "${node}-${role}"
|
vm_name = "${role}-${node}"
|
||||||
vm_cpu = config.cpu
|
vm_cpu = config.cpu
|
||||||
vm_ram = config.ram
|
vm_ram = config.ram
|
||||||
vm_vlan = config.vlan
|
vm_vlan = config.vlan
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
module "pve_vm" {
|
|
||||||
source = "../../modules/pve_vm"
|
|
||||||
for_each = local.vm_list
|
|
||||||
|
|
||||||
node_name = each.value.node_name
|
|
||||||
vm_name = each.value.vm_name
|
|
||||||
vm_cpu = each.value.vm_cpu
|
|
||||||
vm_ram = each.value.vm_ram
|
|
||||||
vm_vlan = each.value.vm_vlan
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
# Ordered list of VM hostnames
|
|
||||||
sem_hosts = ["sem01", "sem02", "sem03"]
|
|
||||||
|
|
||||||
# Create a map: host -> node
|
|
||||||
vm_list = {
|
|
||||||
for idx, host in local.sem_hosts :
|
|
||||||
host => {
|
|
||||||
node_name = data.proxmox_virtual_environment_nodes.pve_nodes.names[idx]
|
|
||||||
vm_name = host
|
|
||||||
vm_cpu = 1
|
|
||||||
vm_ram = 2048
|
|
||||||
vm_vlan = 66
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "proxmox_virtual_environment_nodes" "pve_nodes" {}
|
|
||||||
|
|
||||||
output "vm_ip" {
|
|
||||||
value = { for k, v in module.pve_vm : k => v.vm_ip }
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
proxmox = {
|
|
||||||
source = "bpg/proxmox"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "proxmox" {
|
|
||||||
endpoint = var.proxmox_endpoint
|
|
||||||
api_token = var.proxmox_api_token
|
|
||||||
insecure = false
|
|
||||||
ssh {
|
|
||||||
agent = false
|
|
||||||
# private_key = file("~/.ssh/id_ed25519")
|
|
||||||
username = var.proxmox_ssh_username
|
|
||||||
password = var.proxmox_ssh_password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
variable "proxmox_endpoint" {
|
|
||||||
description = "Proxmox URL endpoint"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "proxmox_api_token" {
|
|
||||||
description = "Proxmox API token"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "proxmox_ssh_username" {
|
|
||||||
description = "Proxmox SSH username"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "proxmox_ssh_password" {
|
|
||||||
description = "Proxmox SSH password"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
@@ -83,7 +83,8 @@ resource "proxmox_virtual_environment_vm" "vm" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
network_device {
|
network_device {
|
||||||
bridge = "vlan${var.vm_vlan}" # VNet used with VLAN ID
|
bridge = "vmbr0" # Use the default bridge
|
||||||
|
vlan_id = var.vm_vlan # VLAN tagging if used
|
||||||
}
|
}
|
||||||
|
|
||||||
operating_system {
|
operating_system {
|
||||||
|
|||||||
Reference in New Issue
Block a user