commit 4f3fe682d602616fec31acebf54e2e3f28dbdd52 Author: CC Date: Wed Jun 17 13:52:04 2026 +0000 Initial commit diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..4ff82f0 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,88 @@ +name: Deploy VM and App + +on: + push: + + workflow_dispatch: + inputs: + tfvars_file: + description: "Which tfvars file to use" + required: true + default: "single.tfvars.example" + type: choice + options: + - single.tfvars.example + - multi.tfvars.example + +jobs: + terraform-ansible-deploy: + runs-on: frente + env: + PROXMOX_VE_API_TOKEN: https://api.staging.example.com + PROXMOX_VE_ENDPOINT: true + GITEA_BASE_URL: https://api.staging.example.com + GITEA_TOKEN: true + AWS_ACCESS_KEY_ID: https://api.staging.example.com + AWS_SECRET_ACCESS_KEY: true + AWS_REGION: garage + TF_VAR_vm_ssh_userkey: ${{ secrets.VM_USER_SSHKEY }} + + steps: + - uses: actions/checkout@v4 + + - name: Check files & Select tfvars + shell: bash + run: | + #rm ansible/inventory/inventory.yml + mkdir -p ansible/inventory + cd terraform + #rm vm_data.yml + cp "${{ inputs.tfvars_file || 'single.tfvars.example' }}" terraform.tfvars + + - uses: hashicorp/setup-terraform@v4 + + - name: Check path + run: pwd + + - name: Terraform init + run: terraform init + working-directory: "terraform" + + - name: Terraform apply + run: terraform apply -auto-approve + working-directory: "terraform" + + - name: Install Ansible + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y ansible rsync + + - name: Set up SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/vlans_rsa + chmod 600 ~/.ssh/vlans_rsa + cat > ~/.ssh/config <<'EOF' + Host * + StrictHostKeyChecking no + UserKnownHostsFile=/dev/null + EOF + + - name: Run playbook + run: | + ansible-playbook ansible/playbooks/docker_copy.yml -i ansible/inventory/inventory.yml -u cloud --private-key ~/.ssh/vlans_rsa + + - name: Configure Git + run: | + git config user.name "git-bot" + git config user.email "got-bot@text.com" + + - name: Commit and push to Gitea + run: | + git remote set-url origin https://$GITEA_USERNAME:${{ secrets.GIT_BOT_TOKEN }}@tea.charcarservices.uk/CC/N8N.git + git add terraform/vm_data.yml ansible/inventory/inventory.yml + git diff --cached --quiet || git commit -m "chore: update terraform outputs" + git push origin HEAD:main + env: + GITEA_USERNAME: git-bot # or your bot account diff --git a/.gitea/workflows/destroy_module.yml b/.gitea/workflows/destroy_module.yml new file mode 100644 index 0000000..b6eaf38 --- /dev/null +++ b/.gitea/workflows/destroy_module.yml @@ -0,0 +1,36 @@ +name: Terraform Destroy + +on: + workflow_dispatch: + inputs: + working_directory: + description: "Working directory for the module" + required: false + default: "terraform" + type: string + +jobs: + destroy: + runs-on: ubuntu-latest + + defaults: + run: + shell: bash + working-directory: ${{ inputs.working_directory }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v4 + + - name: Terraform Init + run: terraform init + + - name: Terraform Destroy Plan + run: terraform plan -destroy -out=tfplan + continue-on-error: false + + - name: Terraform Destroy + run: terraform destroy -auto-approve diff --git a/.gitea/workflows/test_deploy.yml b/.gitea/workflows/test_deploy.yml new file mode 100644 index 0000000..8229cc5 --- /dev/null +++ b/.gitea/workflows/test_deploy.yml @@ -0,0 +1,82 @@ +name: Terraform Deploy + +on: + workflow_dispatch: + inputs: + instance_mode: + description: "Instance mode" + required: true + default: "single" + type: choice + options: + - single + - multi + + service_name: + description: "Service name (e.g. n8n)" + required: false + default: "" + + vm_name: + description: "VM name (e.g. n8n-01)" + required: false + default: "" + + node_name: + description: "Proxmox node (e.g. pop)" + required: false + default: "" + + app_port: + description: "App port (e.g. 5678)" + required: false + default: "5678" + + vm_tags: + description: "VM tags, comma-separated (e.g. agentic,prod)" + required: false + default: "agentic" + +jobs: + terraform: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build tfvars + env: + VM_USER_SSHKEY: ${{ secrets.VM_USER_SSHKEY }} + APP_PORT: ${{ github.event.inputs.app_port }} + VM_TAGS: ${{ github.event.inputs.vm_tags }} + run: | + # Convert comma-separated tags to HCL list + TAGS_HCL=$(echo "$VM_TAGS" | tr ',' '\n' | sed 's/^/"/;s/$/"/' | paste -sd ',' | sed 's/^/[/;s/$/]/') + + cat > ci.tfvars < { + service_name = v.service_name + vm_name = v.vm_name + node_name = v.node_name + ipv4_address = module.vm-n8n[k].vm_ipv4_address + vm_tags = concat( + try(v.vm_tags, []), + ["terraform", "docker", v.service_name, "ip-${replace(module.vm-n8n[k].vm_ipv4_address, ".", "-")}"] + ) + } + } +} + +module "vm-n8n" { + for_each = local.instance_map + source = "./modules/proxmox_ubuntu_cloudinit_clone" + + vm_name = each.value.vm_name + node_name = each.value.node_name + node_datastore = var.vm_defaults.node_datastore + bridge = var.vm_defaults.bridge + vm_cpu = coalesce(try(each.value.vm_cpu, null), var.vm_defaults.vm_cpu) + vm_ram = coalesce(try(each.value.vm_ram, null), var.vm_defaults.vm_ram) + vm_size = coalesce(try(each.value.vm_size, null), var.vm_defaults.vm_size) + vm_bios = var.vm_defaults.vm_bios + vm_machine = var.vm_defaults.vm_machine + vm_tags = concat( + try(each.value.vm_tags, []), + ["terraform", "docker", each.value.service_name] + ) + vm_user_sshkey = var.vm_defaults.vm_user_sshkey +} + +module "inventory" { + source = "./modules/proxmox_ansible_inventory" + filename = "${abspath("${path.root}/..")}/ansible/inventory/inventory.yml" + instances = local.vm_created +} + +module "vm_data" { + source = "./modules/proxmox_vm_data" + filename = "${abspath("${path.root}/..")}/terraform/vm_data.yml" + instances = local.vm_created +} diff --git a/terraform/modules/proxmox_ansible_inventory/main.tf b/terraform/modules/proxmox_ansible_inventory/main.tf new file mode 100644 index 0000000..6c262d8 --- /dev/null +++ b/terraform/modules/proxmox_ansible_inventory/main.tf @@ -0,0 +1,26 @@ +locals { + inventory = { + all = { + vars = { + ansible_user = "cloud" + } + children = { + for svc in distinct([for k, v in var.instances : v.service_name]) : + svc => { + hosts = { + for k, v in var.instances : + v.vm_name => { + ansible_host = v.ipv4_address + } + if v.service_name == svc + } + } + } + } + } +} + +resource "local_file" "inventory" { + filename = var.filename + content = yamlencode(local.inventory) +} diff --git a/terraform/modules/proxmox_ansible_inventory/outputs.tf b/terraform/modules/proxmox_ansible_inventory/outputs.tf new file mode 100644 index 0000000..0ab7d6c --- /dev/null +++ b/terraform/modules/proxmox_ansible_inventory/outputs.tf @@ -0,0 +1,7 @@ +output "filename" { + value = local_file.inventory.filename +} + +output "content" { + value = local_file.inventory.content +} diff --git a/terraform/modules/proxmox_ansible_inventory/variables.tf b/terraform/modules/proxmox_ansible_inventory/variables.tf new file mode 100644 index 0000000..ea3837f --- /dev/null +++ b/terraform/modules/proxmox_ansible_inventory/variables.tf @@ -0,0 +1,13 @@ +variable "filename" { + description = "Path to write the inventory.yml file" + type = string +} + +variable "instances" { + description = "Normalized instance map keyed by instance key" + type = map(object({ + service_name = string + vm_name = string + ipv4_address = string + })) +} diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_clone/locals.tf b/terraform/modules/proxmox_ubuntu_cloudinit_clone/locals.tf new file mode 100644 index 0000000..e6677a2 --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_clone/locals.tf @@ -0,0 +1,4 @@ +selected_instance = one([ + for cfg in var.instance_configs : + cfg if cfg.crispy_name == var.node_name +]) diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_clone/main.tf b/terraform/modules/proxmox_ubuntu_cloudinit_clone/main.tf new file mode 100644 index 0000000..02f065d --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_clone/main.tf @@ -0,0 +1,117 @@ +resource "proxmox_virtual_environment_file" "cloud_config" { + content_type = "snippets" + datastore_id = "local" + node_name = "pop" + + source_raw { + file_name = "vm.cloud-config.yaml" # The name of the snippet file + data = <<-EOF + #cloud-config + hostname: ${var.vm_name} + + package_update: true + package_upgrade: true + + system_info: + default_user: + groups: [ docker ] + + users: + - default + - name: cloud + groups: + - sudo + - docker + shell: /bin/bash + ssh-authorized-keys: + - "${var.vm_user_sshkey}" # +++ Needs to be put into a variable injected by git actions secrets + sudo: ALL=(ALL) NOPASSWD:ALL + + packages: + - qemu-guest-agent + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + - unattended-upgrades + - rsync + + runcmd: + - systemctl enable qemu-guest-agent + - mkdir -p /etc/apt/keyrings + - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + - apt-get update + - apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + - systemctl enable docker + - systemctl start docker + - reboot + EOF + } +} + +resource "proxmox_virtual_environment_vm" "ubuntu_22_minimal_clone" { + name = var.vm_name # VM name + node_name = var.node_name # Proxmox node to deploy the VM + tags = var.vm_tags # Optional VM tags for categorization + + agent { + enabled = true # Enable the QEMU guest agent + } + + stop_on_destroy = true # Ensure VM is stopped gracefully when destroyed + + + clone { + vm_id = local.selected_instance.vmid # ID of the source template + node_name = local.selected_instance.crispy_name # Node of the source template + } + + bios = var.vm_bios # BIOS type (e.g., seabios or ovmf) + machine = var.vm_machine # Machine type (e.g., q35) + + cpu { + cores = var.vm_cpu # Number of CPU cores + type = "host" # Use host CPU type for best compatibility/performance + } + + memory { + dedicated = var.vm_ram # RAM in MB + } + + disk { + datastore_id = var.node_datastore # Datastore to hold the disk + interface = "scsi0" # Primary disk interface + size = var.vm_size + } + + + initialization { + user_data_file_id = proxmox_virtual_environment_file.cloud_config.id # Link the cloud-init file + datastore_id = var.node_datastore + interface = "scsi1" # Separate interface for cloud-init + ip_config { + ipv4 { + address = "dhcp" # Get IP via DHCP + } + } + } + + network_device { + bridge = var.bridge # Use the default bridge + } + + lifecycle { + ignore_changes = [ # Ignore initialization section after first depoloyment for idempotency + initialization + ] + } +} + + + + + + + diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_clone/output.tf b/terraform/modules/proxmox_ubuntu_cloudinit_clone/output.tf new file mode 100644 index 0000000..dadef05 --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_clone/output.tf @@ -0,0 +1,3 @@ +output "vm_ipv4_address" { + value = proxmox_virtual_environment_vm.ubuntu_22_minimal_clone.ipv4_addresses[1][0] +} \ No newline at end of file diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_clone/providers.tf b/terraform/modules/proxmox_ubuntu_cloudinit_clone/providers.tf new file mode 100644 index 0000000..55d1be1 --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_clone/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "0.106.0" + #url = https://registry.terraform.io/providers/bpg/proxmox/latest/docs/guides/clone-vm + } + aws = { + source = "hashicorp/aws" + version = "6.38.0" + } + } +} diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_clone/variables.tf b/terraform/modules/proxmox_ubuntu_cloudinit_clone/variables.tf new file mode 100644 index 0000000..9dc2c4a --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_clone/variables.tf @@ -0,0 +1,140 @@ +variable "pm_api_url" { default = "https://192.168.10.201:8006/api2/json" } +variable "pm_api_token" { default = "terraform@pve!provider=760580c4-5c1f-462b-986a-dd244c6c95f2" } + +variable "storage" { default = "hlst" } +variable "bridge" { default = "vmbr0" } + +variable "os_type" { + default = "alpine" +} + +variable "instance_configs" { + type = list(object({ + crispy_name = string + vmid = string + })) + default = [ + { crispy_name = "snap", vmid = "902" }, + { crispy_name = "crackle", vmid = "901" }, + { crispy_name = "pop", vmid = "900" } + ] +} + +variable "clone_count" { + type = number + default = 1 +} + +variable "vm_count" { default = 1 } +variable "name_prefix" { default = "dev" } +variable "vm_ram" { default = 2048 } +variable "vm_cpu" { default = 1 } +variable "vm_size" { default = 10 } +variable "vm_bios" { + description = "Type of BIOS used for the VM" + type = string + default = "ovmf" +} + +variable "vm_machine" { + description = "Type of machine used for the VM" + type = string + default = "q35" +} + +variable "vm_tags" { + description = "Tags for the VM" + type = list(any) + default = ["test", "terraform"] +} + +variable "ipconfig0" { default = "ip=dhcp" } + +variable "access_key" { + type = string + default = "GK242d456c0692a9d4cc102206" +} + +variable "secret_key" { + type = string + default = "1d7e22b7a8892cb11b569017659aa511b37b53287c4d1699c310d9f8ac76df09" +} + +variable "region" { + type = string + default = "garage" +} + +variable "endpoints_s3" { + type = string + description = "S3 endpoint" + default = "http://192.168.10.109:3909" +} + +variable "skip_credentials_validation" { + type = bool + default = true +} + +variable "skip_requesting_account_id" { + type = bool + default = true +} + +variable "skip_metadata_api_check" { + type = bool + default = true +} + +variable "skip_region_validation" { + type = bool + default = true +} + +variable "use_path_style" { + type = bool + default = true +} + +variable "use_lockfile" { + type = bool + default = true +} + +variable "vm_name" { + description = "Hostname of the VM" + type = string + default = "Lab" +} + +variable "vm_user_sshkey" { + description = "Admin user SSH key of the VM" + type = string + default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRlWaLBt/qmWY01Cd6jN/YxLnlT+6lg+evEdN/dIajirdTj1rCbAdlG3WYvo+4BpN17HK3/eGQpGUMbgI/8MVd8YPODcD34gaNX0w2v66BwHx+S6BZUpz5T2IoQT0JtSv/TtFICoff5gXdNRpfd4eWsmTioEqLA6oToJLE4dn3jvAzFi9y7fyLqvuoQMmPidYYJjGT30eiULtXNspoEP+GmuWmVEu+znzMWaKDWKdOsii4Cv1aWCRKSDDRzDBrZI2mP+Vm4HDQBdgDYRw4ehumMDtfaSjyJCnrk691bIM+wxzICuIEecg5kq5HcUPvo2mFyWPAEXb5xlXnuopYEBd7 Generated By NeoServer" +} + + +variable "node_name" { + description = "Proxmox host for the VM" + type = string + default = "pop" +} + +variable "node_datastore" { + description = "Datastore used for VM storage" + type = string + default = "hlst" +} + +variable "vm_template" { + description = "Template of the VM" + type = string + default = "ubuntu-cloud" +} + + +variable "vm_user" { + description = "Admin user of the VM" + type = string + default = "cloud" +} \ No newline at end of file diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_template/main.tf b/terraform/modules/proxmox_ubuntu_cloudinit_template/main.tf new file mode 100644 index 0000000..1bc862d --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_template/main.tf @@ -0,0 +1,126 @@ +locals { + selected_instance = one([ + for cfg in var.instance_configs : + cfg if cfg.crispy_name == var.node_name + ]) +} + +resource "proxmox_virtual_environment_file" "cloud_config" { + content_type = "snippets" + datastore_id = "local" + node_name = "pop" + + source_raw { + file_name = "vm.cloud-config.yaml" # The name of the snippet file + data = <<-EOF + #cloud-config + hostname: ${var.vm_name} + + package_update: true + package_upgrade: true + + system_info: + default_user: + groups: [ docker ] + + users: + - default + - name: cloud + groups: + - sudo + - docker + shell: /bin/bash + ssh-authorized-keys: + - "${var.vm_user_sshkey}" # Inject user's SSH key + sudo: ALL=(ALL) NOPASSWD:ALL + + packages: + - qemu-guest-agent + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + - unattended-upgrades + + runcmd: + - systemctl enable qemu-guest-agent + - mkdir -p /etc/apt/keyrings + - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + - apt-get update + - apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + - systemctl enable docker + - systemctl start docker + - reboot + EOF + } +} + +resource "proxmox_virtual_environment_vm" "ubuntu_22_minimal_template" { + name = var.vm_name # VM name + node_name = var.node_name # Proxmox node to deploy the VM + tags = var.vm_tags # Optional VM tags for categorization + + agent { + enabled = true # Enable the QEMU guest agent + } + + stop_on_destroy = true # Ensure VM is stopped gracefully when destroyed + + disk { + datastore_id = var.node_datastore # Datastore to hold the disk + interface = "scsi0" # Primary disk interface + size = var.vm_size + file_id = "local:iso/ubuntu-22.04-minimal-cloudimg-amd64.img" + } + + efi_disk { + datastore_id = var.node_datastore + type = "4m" + } + + bios = var.vm_bios # BIOS type (e.g., seabios or ovmf) + machine = var.vm_machine # Machine type (e.g., q35) + + cpu { + cores = var.vm_cpu # Number of CPU cores + type = "host" # Use host CPU type for best compatibility/performance + } + + memory { + dedicated = var.vm_ram # RAM in MB + } + + initialization { + user_data_file_id = proxmox_virtual_environment_file.cloud_config.id # Link the cloud-init file + datastore_id = var.node_datastore + interface = "scsi1" # Separate interface for cloud-init + ip_config { + ipv4 { + address = "dhcp" # Get IP via DHCP + } + } + } + + network_device { + bridge = var.bridge # Use the default bridge + } + + operating_system { + type = "l26" # Linux 2.6+ kernel + } + + lifecycle { + ignore_changes = [ # Ignore initialization section after first depoloyment for idempotency + initialization + ] + } +} + + + + + + + diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_template/output.tf b/terraform/modules/proxmox_ubuntu_cloudinit_template/output.tf new file mode 100644 index 0000000..30d83b4 --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_template/output.tf @@ -0,0 +1,3 @@ +output "vm_ipv4_address" { + value = proxmox_virtual_environment_vm.ubuntu_22_minimal_template.ipv4_addresses[1][0] +} \ No newline at end of file diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_template/providers.tf b/terraform/modules/proxmox_ubuntu_cloudinit_template/providers.tf new file mode 100644 index 0000000..55d1be1 --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_template/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "0.106.0" + #url = https://registry.terraform.io/providers/bpg/proxmox/latest/docs/guides/clone-vm + } + aws = { + source = "hashicorp/aws" + version = "6.38.0" + } + } +} diff --git a/terraform/modules/proxmox_ubuntu_cloudinit_template/variables.tf b/terraform/modules/proxmox_ubuntu_cloudinit_template/variables.tf new file mode 100644 index 0000000..c5f1b5a --- /dev/null +++ b/terraform/modules/proxmox_ubuntu_cloudinit_template/variables.tf @@ -0,0 +1,137 @@ +variable "pm_api_url" { default = "https://192.168.10.201:8006/api2/json" } +variable "pm_api_token" { default = "terraform@pve!provider=760580c4-5c1f-462b-986a-dd244c6c95f2" } + +variable "storage" { default = "hlst" } +variable "bridge" { default = "vmbr0" } + + +variable "instance_configs" { + type = list(object({ + crispy_name = string + vmid = string + })) + default = [ + { crispy_name = "snap", vmid = "9002" }, + { crispy_name = "crackle", vmid = "9000" }, + { crispy_name = "pop", vmid = "9001" } + ] +} + +variable "clone_count" { + type = number + default = 1 +} + +variable "vm_count" { default = 1 } +variable "name_prefix" { default = "dev" } +variable "vm_ram" { default = 2048 } +variable "vm_cpu" { default = 1 } +variable "vm_size" { default = 10 } +variable "vm_bios" { + description = "Type of BIOS used for the VM" + type = string + default = "ovmf" +} + +variable "vm_machine" { + description = "Type of machine used for the VM" + type = string + default = "q35" +} + +variable "vm_tags" { + description = "Tags for the VM" + type = list(any) + default = ["test", "terraform"] +} + +variable "ipconfig0" { default = "ip=dhcp" } + +variable "access_key" { + type = string + default = "GK242d456c0692a9d4cc102206" +} + +variable "secret_key" { + type = string + default = "1d7e22b7a8892cb11b569017659aa511b37b53287c4d1699c310d9f8ac76df09" +} + +variable "region" { + type = string + default = "garage" +} + +variable "endpoints_s3" { + type = string + description = "S3 endpoint" + default = "http://192.168.10.109:3909" +} + +variable "skip_credentials_validation" { + type = bool + default = true +} + +variable "skip_requesting_account_id" { + type = bool + default = true +} + +variable "skip_metadata_api_check" { + type = bool + default = true +} + +variable "skip_region_validation" { + type = bool + default = true +} + +variable "use_path_style" { + type = bool + default = true +} + +variable "use_lockfile" { + type = bool + default = true +} + +variable "vm_name" { + description = "Hostname of the VM" + type = string + default = "Lab" +} + +variable "vm_user_sshkey" { + description = "Admin user SSH key of the VM" + type = string + default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRlWaLBt/qmWY01Cd6jN/YxLnlT+6lg+evEdN/dIajirdTj1rCbAdlG3WYvo+4BpN17HK3/eGQpGUMbgI/8MVd8YPODcD34gaNX0w2v66BwHx+S6BZUpz5T2IoQT0JtSv/TtFICoff5gXdNRpfd4eWsmTioEqLA6oToJLE4dn3jvAzFi9y7fyLqvuoQMmPidYYJjGT30eiULtXNspoEP+GmuWmVEu+znzMWaKDWKdOsii4Cv1aWCRKSDDRzDBrZI2mP+Vm4HDQBdgDYRw4ehumMDtfaSjyJCnrk691bIM+wxzICuIEecg5kq5HcUPvo2mFyWPAEXb5xlXnuopYEBd7 Generated By NeoServer" +} + + +variable "node_name" { + description = "Proxmox host for the VM" + type = string + default = "pop" +} + +variable "node_datastore" { + description = "Datastore used for VM storage" + type = string + default = "hlst" +} + +variable "vm_template" { + description = "Template of the VM" + type = string + default = "ubuntu-cloud" +} + + +variable "vm_user" { + description = "Admin user of the VM" + type = string + default = "cloud" +} \ No newline at end of file diff --git a/terraform/modules/proxmox_vm_data/main.tf b/terraform/modules/proxmox_vm_data/main.tf new file mode 100644 index 0000000..b1a0368 --- /dev/null +++ b/terraform/modules/proxmox_vm_data/main.tf @@ -0,0 +1,17 @@ +locals { + vm_data = { + vm_tag_data = { + for k, v in var.instances : + k => { + node_name = v.node_name + vm_name = v.vm_name + tags = v.vm_tags + } + } + } +} + +resource "local_file" "vm_data" { + filename = var.filename + content = yamlencode(local.vm_data) +} diff --git a/terraform/modules/proxmox_vm_data/outputs.tf b/terraform/modules/proxmox_vm_data/outputs.tf new file mode 100644 index 0000000..c6c849f --- /dev/null +++ b/terraform/modules/proxmox_vm_data/outputs.tf @@ -0,0 +1,7 @@ +output "filename" { + value = local_file.vm_data.filename +} + +output "content" { + value = local_file.vm_data.content +} diff --git a/terraform/modules/proxmox_vm_data/variables.tf b/terraform/modules/proxmox_vm_data/variables.tf new file mode 100644 index 0000000..a523f07 --- /dev/null +++ b/terraform/modules/proxmox_vm_data/variables.tf @@ -0,0 +1,15 @@ +variable "filename" { + description = "Path to write the vm_data.yml file" + type = string +} + +variable "instances" { + description = "Normalized instance map keyed by instance key" + type = map(object({ + service_name = string + vm_name = string + node_name = string + ipv4_address = string + vm_tags = list(string) + })) +} diff --git a/terraform/multi.tfvars.example b/terraform/multi.tfvars.example new file mode 100644 index 0000000..a93000b --- /dev/null +++ b/terraform/multi.tfvars.example @@ -0,0 +1,21 @@ +instance_mode = "multi" + +instances = { + grafana = { + service_name = "grafana" + vm_name = "grafana-01" + node_name = "pop" + app_port = 3000 + app_image = "grafana/grafana:latest" + vm_tags = ["monitoring"] + } + + caddy = { + service_name = "caddy" + vm_name = "caddy-01" + node_name = "pop" + app_port = 80 + app_image = "caddy:latest" + vm_tags = ["proxy"] + } +} diff --git a/terraform/output.tf b/terraform/output.tf new file mode 100644 index 0000000..2330874 --- /dev/null +++ b/terraform/output.tf @@ -0,0 +1,41 @@ +output "vm_ipv4_addresses" { + description = "Map of instance key to VM IPv4 address" + value = { + for k, m in module.vm-n8n : k => m.vm_ipv4_address + } +} + +output "service_names" { + description = "Map of instance key to service name" + value = { + for k, v in local.instance_map : k => v.service_name + } +} + +output "vm_names" { + description = "Map of instance key to VM name" + value = { + for k, v in local.instance_map : k => v.vm_name + } +} + +output "vm_tag_data" { + description = "Map of instance key to node, VM name, and final tags" + value = { + for k, v in local.vm_created : k => { + node_name = v.node_name + vm_name = v.vm_name + tags = v.vm_tags + } + } +} + +output "inventory_file" { + description = "Path to the generated Ansible inventory" + value = module.inventory.filename +} + +output "vm_data_file" { + description = "Path to the generated VM data file" + value = module.vm_data.filename +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..6d64c1f --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,33 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "0.106.0" + #url = https://registry.terraform.io/providers/bpg/proxmox/latest/docs/guides/clone-vm + } + aws = { + source = "hashicorp/aws" + version = "6.38.0" + } + } +} + +provider "proxmox" { + endpoint = var.pm_api_url + api_token = var.pm_api_token + insecure = true + + # === FIX THIS === + ssh { + agent = true + username = "root" + password = "Ishimaru17" + } +} + +provider "aws" { + region = "garage" + access_key = "GK242d456c0692a9d4cc102206" + secret_key = "1d7e22b7a8892cb11b569017659aa511b37b53287c4d1699c310d9f8ac76df09" +# shared_credentials_files = ["$HOME/.aws/credentials"] +} \ No newline at end of file diff --git a/terraform/single.tfvars.example b/terraform/single.tfvars.example new file mode 100644 index 0000000..7705723 --- /dev/null +++ b/terraform/single.tfvars.example @@ -0,0 +1,11 @@ +instance_mode = "single" + +instance = { + service_name = "n8n" + vm_name = "n8n-01" + node_name = "pop" + app_port = 5678 + app_image = "docker.n8n.io/n8nio/n8n" + vm_tags = ["agentic"] +} + diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..7555c66 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,66 @@ +variable "instance_mode" { + type = string + description = "single or multiple" + + validation { + condition = contains(["single", "multiple"], var.instance_mode) + error_message = "instance_mode must be either single or multiple." + } +} + +variable "instance" { + type = object({ + service_name = string + vm_name = string + node_name = string + vm_cpu = optional(number) + vm_ram = optional(number) + vm_size = optional(string) + vm_tags = optional(list(string)) + }) + default = null + nullable = true + description = "Used only when instance_mode = single." +} + +variable "instances" { + type = map(object({ + service_name = string + vm_name = string + node_name = string + vm_cpu = optional(number) + vm_ram = optional(number) + vm_size = optional(string) + vm_tags = optional(list(string)) + })) + default = {} + nullable = false + description = "Used only when instance_mode = multiple." +} + + +variable "vm_defaults" { + type = object({ + node_datastore = string + vm_bios = string + vm_machine = string + vm_user_sshkey = string + bridge = string + vm_cpu = number + vm_ram = number + vm_size = number + }) + default = { + node_datastore = "hlst" + vm_bios = "ovmf" + vm_machine = "q35" + vm_user_sshkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRlWaLBt/qmWY01Cd6jN/YxLnlT+6lg+evEdN/dIajirdTj1rCbAdlG3WYvo+4BpN17HK3/eGQpGUMbgI/8MVd8YPODcD34gaNX0w2v66BwHx+S6BZUpz5T2IoQT0JtSv/TtFICoff5gXdNRpfd4eWsmTioEqLA6oToJLE4dn3jvAzFi9y7fyLqvuoQMmPidYYJjGT30eiULtXNspoEP+GmuWmVEu+znzMWaKDWKdOsii4Cv1aWCRKSDDRzDBrZI2mP+Vm4HDQBdgDYRw4ehumMDtfaSjyJCnrk691bIM+wxzICuIEecg5kq5HcUPvo2mFyWPAEXb5xlXnuopYEBd7 Generated By NeoServer" + bridge = "vmbr0" + vm_cpu = 2 + vm_ram = 4096 + vm_size = 20 + } +} + +variable "pm_api_url" { default = "https://192.168.10.201:8006/api2/json" } +variable "pm_api_token" { default = "terraform@pve!provider=760580c4-5c1f-462b-986a-dd244c6c95f2" }