From 97e7d65f42cc5b09fd4598dc32caa51d1824e11e Mon Sep 17 00:00:00 2001 From: Lino Silva Date: Mon, 30 Mar 2026 10:54:16 +0100 Subject: [PATCH] Initial commit: VM Creation done --- .gitignore | 2 + ansible/README.md | 0 ansible/ansible.cfg | 4 ++ ansible/inventories/group_vars/all/main.yml | 18 +++++++ ansible/inventories/group_vars/all/vault.yml | 9 ++++ ansible/inventories/group_vars/all/vms.yml | 53 +++++++++++++++++++ ansible/inventories/host_vars/edge-1/main.yml | 8 +++ ansible/inventories/host_vars/edge-2/main.yml | 8 +++ ansible/inventories/hosts.ini | 17 ++++++ ansible/inventories/production.yml | 30 +++++++++++ ansible/playbooks/provision_vms.yml | 31 +++++++++++ ansible/roles/base/tasks/main.yml | 20 +++++++ ansible/roles/docker/tasks/main.yml | 11 ++++ ansible/roles/keepalived/handlers/main.yml | 5 ++ ansible/roles/keepalived/tasks/main.yml | 19 +++++++ .../keepalived/templates/keepalived.conf.j2 | 16 ++++++ ansible/roles/komodo/tasks/main.yml | 6 +++ ansible/roles/proxmox_vm/tasks/disk.yml | 11 ++++ ansible/roles/proxmox_vm/tasks/igpu.yml | 5 ++ ansible/roles/proxmox_vm/tasks/main.yml | 53 +++++++++++++++++++ ansible/roles/proxmox_vm/tasks/start.yml | 9 ++++ ansible/roles/traefik/tasks/main.yml | 39 ++++++++++++++ .../traefik/templates/docker-compose.yml.j2 | 36 +++++++++++++ .../roles/traefik/templates/traefik.yml.j2 | 33 ++++++++++++ ansible/roles/vm_apps/tasks/main.yml | 11 ++++ ansible/roles/vm_infra/tasks/main.yml | 10 ++++ ansible/roles/vm_plex/tasks/main.yml | 10 ++++ 27 files changed, 474 insertions(+) create mode 100644 .gitignore create mode 100644 ansible/README.md create mode 100644 ansible/ansible.cfg create mode 100644 ansible/inventories/group_vars/all/main.yml create mode 100644 ansible/inventories/group_vars/all/vault.yml create mode 100644 ansible/inventories/group_vars/all/vms.yml create mode 100644 ansible/inventories/host_vars/edge-1/main.yml create mode 100644 ansible/inventories/host_vars/edge-2/main.yml create mode 100644 ansible/inventories/hosts.ini create mode 100644 ansible/inventories/production.yml create mode 100644 ansible/playbooks/provision_vms.yml create mode 100644 ansible/roles/base/tasks/main.yml create mode 100644 ansible/roles/docker/tasks/main.yml create mode 100644 ansible/roles/keepalived/handlers/main.yml create mode 100644 ansible/roles/keepalived/tasks/main.yml create mode 100644 ansible/roles/keepalived/templates/keepalived.conf.j2 create mode 100644 ansible/roles/komodo/tasks/main.yml create mode 100644 ansible/roles/proxmox_vm/tasks/disk.yml create mode 100644 ansible/roles/proxmox_vm/tasks/igpu.yml create mode 100644 ansible/roles/proxmox_vm/tasks/main.yml create mode 100644 ansible/roles/proxmox_vm/tasks/start.yml create mode 100644 ansible/roles/traefik/tasks/main.yml create mode 100644 ansible/roles/traefik/templates/docker-compose.yml.j2 create mode 100644 ansible/roles/traefik/templates/traefik.yml.j2 create mode 100644 ansible/roles/vm_apps/tasks/main.yml create mode 100644 ansible/roles/vm_infra/tasks/main.yml create mode 100644 ansible/roles/vm_plex/tasks/main.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d41cef5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vault_pass +*.retry diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..2c5fedf --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,4 @@ +[defaults] +roles_path = ./roles +inventory = ./inventories/production.yml +host_key_checking = False diff --git a/ansible/inventories/group_vars/all/main.yml b/ansible/inventories/group_vars/all/main.yml new file mode 100644 index 0000000..8c99661 --- /dev/null +++ b/ansible/inventories/group_vars/all/main.yml @@ -0,0 +1,18 @@ +proxmox_api_host: "10.0.2.2" +proxmox_api_user: "root@pam" +proxmox_api_token_id: "ansible" +proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}" +proxmox_node: "pve" + +template_name: "debian-13-cloudinit" + +ansible_user: root +ansible_python_interpreter: /opt/homebrew/bin/python3 + +docker_packages: + - docker.io + - docker-compose-plugin + +# Traefik configuration +domain: "example.com" # Change to your domain +letsencrypt_email: "admin@example.com" # Change to your email diff --git a/ansible/inventories/group_vars/all/vault.yml b/ansible/inventories/group_vars/all/vault.yml new file mode 100644 index 0000000..8add006 --- /dev/null +++ b/ansible/inventories/group_vars/all/vault.yml @@ -0,0 +1,9 @@ +$ANSIBLE_VAULT;1.1;AES256 +30633439346434353439346639633764626635653563666538653835633838643731666435303334 +3661326333363964633038303533316334363830303236350a393538306461356533636565353031 +30643036363662376661656462386235383438623533303139343037616436666161653530376639 +3063343131616333620a336430323062383738663130623139323633613035643539333565663730 +30613964353338653663373234616365303165306166373034633264303235366433396130616435 +66636161373639393166386331346639666361316237353965373562643761613064666566343436 +65353164316262313234653764353837643763363132383935323231376538383933316563326537 +35343235336163653435 diff --git a/ansible/inventories/group_vars/all/vms.yml b/ansible/inventories/group_vars/all/vms.yml new file mode 100644 index 0000000..725f771 --- /dev/null +++ b/ansible/inventories/group_vars/all/vms.yml @@ -0,0 +1,53 @@ +vms: + # infra-core-1: + # vmid: 410 + # node: purah + # cores: 4 + # memory: 8192 + # disk: 50G + # ip: 10.0.4.10 + # network_bridge: "vmbr0" + # storage: purah-mirror-860gb + + # media-1: + # vmid: 420 + # node: purah + # cores: 8 + # memory: 16384 + # disk: 200G + # ip: 10.0.4.20 + # igpu: true + # network_bridge: "vmbr0" + # storage: purah-mirror-860gb + + # apps-1: + # vmid: 430 + # node: yunobo + # cores: 6 + # memory: 16384 + # disk: 100G + # ip: 10.0.4.30 + # network_bridge: "vmbr2" + # storage: nvme-2tb + + edge-1: + vmid: 401 + node: mipha + template_vmid: 9001 + cores: 2 + memory: 4096 + disk: 30G + ip: 10.0.4.1 + network_bridge: "vmbr0" + storage: "local-lvm" + + edge-2: + vmid: 402 + node: sidon + template_vmid: 9002 + cores: 2 + memory: 4096 + disk: 30G + ip: 10.0.4.2 + network_bridge: "vmbr0" + storage: "local-lvm" diff --git a/ansible/inventories/host_vars/edge-1/main.yml b/ansible/inventories/host_vars/edge-1/main.yml new file mode 100644 index 0000000..1fb8638 --- /dev/null +++ b/ansible/inventories/host_vars/edge-1/main.yml @@ -0,0 +1,8 @@ +--- +# Keepalived configuration for edge-1 (PRIMARY) +keepalived_state: MASTER +keepalived_priority: 100 +keepalived_interface: eth0 +keepalived_router_id: 51 +keepalived_vip: 10.0.4.254 +keepalived_password: "{{ vault_keepalived_password | default('changeme') }}" diff --git a/ansible/inventories/host_vars/edge-2/main.yml b/ansible/inventories/host_vars/edge-2/main.yml new file mode 100644 index 0000000..573e101 --- /dev/null +++ b/ansible/inventories/host_vars/edge-2/main.yml @@ -0,0 +1,8 @@ +--- +# Keepalived configuration for edge-2 (BACKUP) +keepalived_state: BACKUP +keepalived_priority: 50 +keepalived_interface: eth0 +keepalived_router_id: 51 +keepalived_vip: 10.0.4.254 +keepalived_password: "{{ vault_keepalived_password | default('changeme') }}" diff --git a/ansible/inventories/hosts.ini b/ansible/inventories/hosts.ini new file mode 100644 index 0000000..488b3b9 --- /dev/null +++ b/ansible/inventories/hosts.ini @@ -0,0 +1,17 @@ +[ms01] +purah ansible_host=10.0.2.5 +yunobo ansible_host=10.0.3.4 + +[nuc] +mipha ansible_host=10.0.2.3 +sidon ansible_host=10.0.2.2 +yuga ansible_host=10.0.2.7 + +[infra] +infra-core-1 ansible_host=10.0.4.1 + +[media] +media-1 ansible_host=10.0.4.10 + +[apps] +apps-1 ansible_host=10.0.4.20 diff --git a/ansible/inventories/production.yml b/ansible/inventories/production.yml new file mode 100644 index 0000000..692fe89 --- /dev/null +++ b/ansible/inventories/production.yml @@ -0,0 +1,30 @@ +all: + hosts: + localhost: + ansible_connection: local + children: + edge: + hosts: + edge-1: + ansible_host: 10.0.4.1 + edge-2: + ansible_host: 10.0.4.2 + purah: + hosts: + infra-core-1: + ansible_host: 10.0.4.10 + yunobo: + hosts: + media-1: + ansible_host: 10.0.4.20 + apps-1: + ansible_host: 10.0.4.30 + mipha: + hosts: + edge-1: + ansible_host: 10.0.4.1 + sidon: + hosts: + edge-2: + ansible_host: 10.0.4.2 + diff --git a/ansible/playbooks/provision_vms.yml b/ansible/playbooks/provision_vms.yml new file mode 100644 index 0000000..be60090 --- /dev/null +++ b/ansible/playbooks/provision_vms.yml @@ -0,0 +1,31 @@ +--- +- hosts: localhost + gather_facts: no + roles: + - proxmox_vm + +- hosts: edge + become: yes + roles: + - base + - docker + - keepalived + - traefik + +# - hosts: all +# become: yes +# roles: +# - base +# - docker +# - komodo + +# - hosts: purah +# become: yes +# roles: +# - vm_infra +# - vm_plex + +# - hosts: yunobo +# become: yes +# roles: +# - vm_apps diff --git a/ansible/roles/base/tasks/main.yml b/ansible/roles/base/tasks/main.yml new file mode 100644 index 0000000..e3e00f5 --- /dev/null +++ b/ansible/roles/base/tasks/main.yml @@ -0,0 +1,20 @@ +--- +- name: Update apt cache + ansible.builtin.apt: + update_cache: yes + +- name: Install essential packages + ansible.builtin.apt: + name: + - sudo + - vim + - curl + - git + - software-properties-common + state: present + +- name: Ensure user is in docker group + ansible.builtin.user: + name: "{{ ansible_user }}" + groups: docker + append: yes diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml new file mode 100644 index 0000000..2562aa8 --- /dev/null +++ b/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Install Docker & Compose + ansible.builtin.apt: + name: "{{ docker_packages }}" + state: present + +- name: Ensure Docker service is started + ansible.builtin.service: + name: docker + state: started + enabled: yes diff --git a/ansible/roles/keepalived/handlers/main.yml b/ansible/roles/keepalived/handlers/main.yml new file mode 100644 index 0000000..fe7d01d --- /dev/null +++ b/ansible/roles/keepalived/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart keepalived + systemd: + name: keepalived + state: restarted diff --git a/ansible/roles/keepalived/tasks/main.yml b/ansible/roles/keepalived/tasks/main.yml new file mode 100644 index 0000000..618f503 --- /dev/null +++ b/ansible/roles/keepalived/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Install keepalived + apt: + name: keepalived + state: present + update_cache: yes + +- name: Create keepalived config + template: + src: keepalived.conf.j2 + dest: /etc/keepalived/keepalived.conf + mode: '0644' + notify: restart keepalived + +- name: Enable and start keepalived + systemd: + name: keepalived + enabled: yes + state: started diff --git a/ansible/roles/keepalived/templates/keepalived.conf.j2 b/ansible/roles/keepalived/templates/keepalived.conf.j2 new file mode 100644 index 0000000..a3a7a3e --- /dev/null +++ b/ansible/roles/keepalived/templates/keepalived.conf.j2 @@ -0,0 +1,16 @@ +vrrp_instance VI_1 { + state {{ keepalived_state | default('MASTER') }} + interface {{ keepalived_interface | default('eth0') }} + virtual_router_id {{ keepalived_router_id | default('51') }} + priority {{ keepalived_priority | default('100') }} + advert_int 1 + + authentication { + auth_type PASS + auth_pass {{ keepalived_password | default('secret') }} + } + + virtual_ipaddress { + {{ keepalived_vip }} + } +} diff --git a/ansible/roles/komodo/tasks/main.yml b/ansible/roles/komodo/tasks/main.yml new file mode 100644 index 0000000..48728f0 --- /dev/null +++ b/ansible/roles/komodo/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- name: Install Komodo agent + ansible.builtin.shell: | + curl -sSL https://komodo.install/script.sh | bash + args: + creates: /usr/local/bin/komodo diff --git a/ansible/roles/proxmox_vm/tasks/disk.yml b/ansible/roles/proxmox_vm/tasks/disk.yml new file mode 100644 index 0000000..bb797a8 --- /dev/null +++ b/ansible/roles/proxmox_vm/tasks/disk.yml @@ -0,0 +1,11 @@ +- name: Resize disk + community.proxmox.proxmox_disk: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + vmid: "{{ item.value.vmid }}" + disk: scsi0 + size: "{{ item.value.disk }}" + state: resized + loop: "{{ vms | dict2items }}" diff --git a/ansible/roles/proxmox_vm/tasks/igpu.yml b/ansible/roles/proxmox_vm/tasks/igpu.yml new file mode 100644 index 0000000..492f204 --- /dev/null +++ b/ansible/roles/proxmox_vm/tasks/igpu.yml @@ -0,0 +1,5 @@ +- name: Enable iGPU passthrough (media VM) + ansible.builtin.shell: | + qm set {{ item.value.vmid }} -hostpci0 00:02.0 + when: item.value.igpu is defined and item.value.igpu + loop: "{{ vms | dict2items }}" diff --git a/ansible/roles/proxmox_vm/tasks/main.yml b/ansible/roles/proxmox_vm/tasks/main.yml new file mode 100644 index 0000000..adebf1f --- /dev/null +++ b/ansible/roles/proxmox_vm/tasks/main.yml @@ -0,0 +1,53 @@ +--- +- name: Clone VM from template + community.proxmox.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + node: "{{ item.value.node }}" + clone: "debian-13-cloudinit" + vmid: "{{ item.value.template_vmid }}" + newid: "{{ item.value.vmid }}" + storage: "{{ item.value.storage }}" + name: "{{ item.key }}" + full: yes + loop: "{{ vms | dict2items }}" + +- name: Configure VM hardware and cloud-init + community.proxmox.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + node: "{{ item.value.node }}" + vmid: "{{ item.value.vmid }}" + cores: "{{ item.value.cores }}" + memory: "{{ item.value.memory }}" + scsihw: virtio-scsi-pci + net: + net0: "virtio,bridge={{ item.value.network_bridge }}" + ipconfig: + ipconfig0: "ip={{ item.value.ip }}/24,gw=10.0.0.1" + nameservers: "10.0.2.49 10.0.2.50 10.0.2.51" + ciuser: "{{ ansible_user }}" + sshkeys: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}" + update: yes + loop: "{{ vms | dict2items }}" + +- name: Wait for clone to complete + pause: + seconds: 10 + +- include_tasks: disk.yml + +- include_tasks: start.yml + +- name: Wait for VMs to be ready + wait_for: + host: "{{ item.value.ip }}" + port: 22 + delay: 10 + timeout: 300 + loop: "{{ vms | dict2items }}" + delegate_to: localhost diff --git a/ansible/roles/proxmox_vm/tasks/start.yml b/ansible/roles/proxmox_vm/tasks/start.yml new file mode 100644 index 0000000..5893f1a --- /dev/null +++ b/ansible/roles/proxmox_vm/tasks/start.yml @@ -0,0 +1,9 @@ +- name: Start VMs + community.proxmox.proxmox_kvm: + api_host: "{{ proxmox_api_host }}" + api_user: "{{ proxmox_api_user }}" + api_token_id: "{{ proxmox_api_token_id }}" + api_token_secret: "{{ proxmox_api_token_secret }}" + vmid: "{{ item.value.vmid }}" + state: started + loop: "{{ vms | dict2items }}" diff --git a/ansible/roles/traefik/tasks/main.yml b/ansible/roles/traefik/tasks/main.yml new file mode 100644 index 0000000..bc21d1f --- /dev/null +++ b/ansible/roles/traefik/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Create traefik directory + file: + path: /opt/traefik + state: directory + mode: '0755' + +- name: Create traefik data directory + file: + path: /opt/traefik/data + state: directory + mode: '0755' + +- name: Create proxy network + docker_network: + name: proxy + +- name: Create acme.json for Let's Encrypt + file: + path: /opt/traefik/data/acme.json + state: touch + mode: '0600' + +- name: Create traefik config file + template: + src: traefik.yml.j2 + dest: /opt/traefik/data/traefik.yml + mode: '0644' + +- name: Create docker-compose file + template: + src: docker-compose.yml.j2 + dest: /opt/traefik/docker-compose.yml + mode: '0644' + +- name: Start Traefik + community.docker.docker_compose_v2: + project_src: /opt/traefik + state: present diff --git a/ansible/roles/traefik/templates/docker-compose.yml.j2 b/ansible/roles/traefik/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..1332f0b --- /dev/null +++ b/ansible/roles/traefik/templates/docker-compose.yml.j2 @@ -0,0 +1,36 @@ +version: '3.8' + +services: + traefik: + image: traefik:v3.0 + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + networks: + - proxy + ports: + - "80:80" + - "443:443" + - "8080:8080" # Dashboard + volumes: + - /etc/localtime:/etc/localtime:ro + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./data/traefik.yml:/traefik.yml:ro + - ./data/acme.json:/acme.json + labels: + - "traefik.enable=true" + - "traefik.http.routers.traefik.entrypoints=http" + - "traefik.http.routers.traefik.rule=Host(`traefik.{{ domain | default('local') }}`)" + - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https" + - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https" + - "traefik.http.routers.traefik.middlewares=traefik-https-redirect" + - "traefik.http.routers.traefik-secure.entrypoints=https" + - "traefik.http.routers.traefik-secure.rule=Host(`traefik.{{ domain | default('local') }}`)" + - "traefik.http.routers.traefik-secure.tls=true" + - "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare" + - "traefik.http.routers.traefik-secure.service=api@internal" + +networks: + proxy: + external: true diff --git a/ansible/roles/traefik/templates/traefik.yml.j2 b/ansible/roles/traefik/templates/traefik.yml.j2 new file mode 100644 index 0000000..ce4717b --- /dev/null +++ b/ansible/roles/traefik/templates/traefik.yml.j2 @@ -0,0 +1,33 @@ +api: + dashboard: true + debug: true + +entryPoints: + http: + address: ":80" + http: + redirections: + entryPoint: + to: https + scheme: https + https: + address: ":443" + +serversTransport: + insecureSkipVerify: true + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + +certificatesResolvers: + cloudflare: + acme: + email: {{ letsencrypt_email | default('admin@example.com') }} + storage: acme.json + dnsChallenge: + provider: cloudflare + resolvers: + - "1.1.1.1:53" + - "1.0.0.1:53" diff --git a/ansible/roles/vm_apps/tasks/main.yml b/ansible/roles/vm_apps/tasks/main.yml new file mode 100644 index 0000000..34f2e9c --- /dev/null +++ b/ansible/roles/vm_apps/tasks/main.yml @@ -0,0 +1,11 @@ +--- +- name: Create app directories + ansible.builtin.file: + path: "/data/{{ item }}" + state: directory + mode: '0755' + loop: + - paperless + - nextcloud + - mealie + - outline diff --git a/ansible/roles/vm_infra/tasks/main.yml b/ansible/roles/vm_infra/tasks/main.yml new file mode 100644 index 0000000..ee12ffc --- /dev/null +++ b/ansible/roles/vm_infra/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: Create infra directories + ansible.builtin.file: + path: "/data/{{ item }}" + state: directory + mode: '0755' + loop: + - vaultwarden + - pi-hole + - uptime-kuma diff --git a/ansible/roles/vm_plex/tasks/main.yml b/ansible/roles/vm_plex/tasks/main.yml new file mode 100644 index 0000000..db68ea9 --- /dev/null +++ b/ansible/roles/vm_plex/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: Ensure VM has iGPU passthrough (requires Proxmox pre-config) + ansible.builtin.debug: + msg: "Ensure /dev/dri is passed through on this VM: {{ inventory_hostname }}" + +- name: Mount media storage + ansible.builtin.file: + path: /data/media + state: directory + mode: '0755'