From 0d81d80e28baa9d1fc1f30ae8d0b1400a92c4f9f Mon Sep 17 00:00:00 2001 From: Matthew McKinnon Date: Sun, 28 Sep 2025 17:43:23 +1000 Subject: [PATCH] Initial Commit --- .gitignore | 39 ++++++++++++++++++ LICENSE.md | 21 ++++++++++ README.md | 26 ++++++++++++ ansible/.gitignore | 2 + ansible/ansible.cfg | 21 ++++++++++ ansible/ghshr.yml | 24 +++++++++++ ansible/group_vars/all.yml | 14 +++++++ ansible/inventory/hosts.ini | 3 ++ ansible/main.yml | 3 ++ ansible/tasks/base.yml | 53 ++++++++++++++++++++++++ ansible/tasks/pull_image.yml | 17 ++++++++ opentofu/200-github.tf | 40 ++++++++++++++++++ opentofu/prepareEnv.sh | 34 ++++++++++++++++ opentofu/provider.tf | 78 ++++++++++++++++++++++++++++++++++++ 14 files changed, 375 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 ansible/.gitignore create mode 100644 ansible/ansible.cfg create mode 100644 ansible/ghshr.yml create mode 100644 ansible/group_vars/all.yml create mode 100644 ansible/inventory/hosts.ini create mode 100644 ansible/main.yml create mode 100644 ansible/tasks/base.yml create mode 100755 ansible/tasks/pull_image.yml create mode 100644 opentofu/200-github.tf create mode 100755 opentofu/prepareEnv.sh create mode 100644 opentofu/provider.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc9564d --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude override files as they are usually for local changes +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negation +# !example_override.tf + +# Exclude Terraform variable files that may contain sensitive data +**/*.tfvars +**/*.tfvars.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Ignore lock files (optional: keep if you want dependency locking in VCS) +.terraform.lock.hcl + +# IDE and OS specific files +.DS_Store +Thumbs.db +*.swp +*.bak +*.tmp +*.idea/ +.vscode/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f46eae1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Matthew McKinnon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f129777 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +![Header Image](https://miro.medium.com/v2/resize:fit:4000/1*16DgdobhWUUXKzF4fwjOdw.png) + +## 📖 Overview + +This repository provisions and manages the GitHub Self-Hosted Runner for the Comprofix Homelab Infrastructure. This repo is designed to be run manually to build and setup the runner when requuired. + +Built using Infrastructure as Code (IaC) with [OpenTofu](https://opentofu.org/) and [Ansible](https://ansible.com) +--- + +## 🚀 Features + +- Declarative infrastructure management with OpenTofu and Ansible +- Remote state stored in PostgreSQL backend +- Secure injection of secrets into `terraform.auto.tfvars` +--- + +## 🔄 Usage + +1. Checkout repo +2. From the opentofu folder, generate `terraform.auto.tfvars` using the supplied ```prepareEnv.sh``` +3. Run `tofu init`, `tofu fmt`, `tofu validate` +4. Execute `tofu plan` +5. If successful, run `tofu apply` +6. From the ansible folder, run `ansible-playbook main.yml` + + diff --git a/ansible/.gitignore b/ansible/.gitignore new file mode 100644 index 0000000..d853b05 --- /dev/null +++ b/ansible/.gitignore @@ -0,0 +1,2 @@ +roles/ +.env \ No newline at end of file diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..5e93564 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,21 @@ +[defaults] +inventory = inventory/hosts.ini +deprecation_warnings = False +host_key_checking = False +interpreter_python = auto_silent +pipelining = True +display_args_to_stdout = True +remote_user = root +forks = 10 +roles_path = ./roles:~/.ansible/roles:/usr/share/ansible/roles + +[privilege_escalation] +become = True +become_method = sudo +become_user = root +become_ask_pass = False + +[ssh_connection] +ssh_args = -o ControlMaster=auto -o ControlPersist=60s + + diff --git a/ansible/ghshr.yml b/ansible/ghshr.yml new file mode 100644 index 0000000..9aa899d --- /dev/null +++ b/ansible/ghshr.yml @@ -0,0 +1,24 @@ +--- +- name: Prepare all servers + hosts: all + become: true + + pre_tasks: + - name: Setup base system + import_tasks: tasks/base.yml + tags: base_setup + + tasks: + - name: Create ghshr folders + file: + path: "/opt/actions-runner" + state: directory + + - name: Download ans extract the GitHub Actions Runner + ansible.builtin.unarchive: + src: https://github.com/actions/runner/releases/download/v2.328.0/actions-runner-linux-x64-2.328.0.tar.gz + dest: /opt/actions-runner + remote_src: yes + + + \ No newline at end of file diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000..35f9a0b --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,14 @@ +--- +install_packages: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + - vim + - git + - htop + - jq + - mc + - net-tools + - unattended-upgrades \ No newline at end of file diff --git a/ansible/inventory/hosts.ini b/ansible/inventory/hosts.ini new file mode 100644 index 0000000..435753c --- /dev/null +++ b/ansible/inventory/hosts.ini @@ -0,0 +1,3 @@ +[ghshr] +ghshr.comprofix.xyz + diff --git a/ansible/main.yml b/ansible/main.yml new file mode 100644 index 0000000..8260059 --- /dev/null +++ b/ansible/main.yml @@ -0,0 +1,3 @@ +--- +- import_playbook: ghshr.yml + diff --git a/ansible/tasks/base.yml b/ansible/tasks/base.yml new file mode 100644 index 0000000..e0483ac --- /dev/null +++ b/ansible/tasks/base.yml @@ -0,0 +1,53 @@ +--- +- name: Ensure debian-archive-keyring is installed + apt: + name: debian-archive-keyring + state: present + update_cache: yes + become: yes + +- name: Update APT cache + apt: + update_cache: yes + force_apt_get: yes + when: ansible_distribution in ['Debian', 'Ubuntu'] + +- name: Upgrade all installed packages to latest + apt: + name: "*" + state: latest + force_apt_get: yes + become: yes + when: ansible_distribution in ['Debian', 'Ubuntu'] + +- name: Dist-upgrade packages (handle removals and replacements) + apt: + upgrade: dist + force_apt_get: yes + become: yes + when: ansible_distribution in ['Debian', 'Ubuntu'] + +- name: Install required packages + apt: + name: "{{ install_packages }}" + state: present + become: yes + when: ansible_distribution in ['Debian', 'Ubuntu'] + register: apt_result + +- name: Find all EXTERNALLY-MANAGED files under /usr/lib/python* + find: + paths: /usr/lib + patterns: "EXTERNALLY-MANAGED" + file_type: file + recurse: yes + register: externally_managed_files + become: yes + +- name: Delete EXTERNALLY-MANAGED files + file: + path: "{{ item.path }}" + state: absent + loop: "{{ externally_managed_files.files }}" + when: externally_managed_files.matched > 0 + become: yes diff --git a/ansible/tasks/pull_image.yml b/ansible/tasks/pull_image.yml new file mode 100755 index 0000000..c0af824 --- /dev/null +++ b/ansible/tasks/pull_image.yml @@ -0,0 +1,17 @@ +--- +# Reusable snippet for pulling Docker images + +- name: Set {{ image_name }} image reference + set_fact: + "{{ image_var }}_image_ref": "{{ image_ref }}" + +- name: Ensure {{ image_name }} image is pulled + community.docker.docker_image: + name: "{{ image_ref }}" + source: pull + register: pulled_image + +- name: Save image result under dynamic key + set_fact: + container_images: >- + {{ container_images | default({}) | combine({ image_var: pulled_image }) }} diff --git a/opentofu/200-github.tf b/opentofu/200-github.tf new file mode 100644 index 0000000..22ea219 --- /dev/null +++ b/opentofu/200-github.tf @@ -0,0 +1,40 @@ +resource "proxmox_lxc" "ghshr" { + + target_node = "pve" + vmid = "200" + hostname = "ghshr" + ostemplate = "local:vztmpl/debian-13-standard_13.1-1_amd64.tar.zst" + password = var.ci_password + unprivileged = false + ostype = "debian" + onboot = true + start = true + startup = "order=1000" + + + ssh_public_keys = < terraform.auto.tfvars +echo 'proxmox_api_token_id = "'$proxmox_api_token_id'"' >> terraform.auto.tfvars +echo 'proxmox_api_token_secret = "'$proxmox_api_token_secret'"' >> terraform.auto.tfvars +echo 'ci_user = "'$ci_user'"' >> terraform.auto.tfvars +echo 'ci_password = "'$ci_password'"' >> terraform.auto.tfvars +echo 'ssh_key = "'$ssh_key'"' >> terraform.auto.tfvars +echo 'passphrase = "'$passphrase'"' >> terraform.auto.tfvars + +export PG_CONN_STR="postgres://$tfusername:$tfpassword@$tfurl" + + + + + diff --git a/opentofu/provider.tf b/opentofu/provider.tf new file mode 100644 index 0000000..7af8831 --- /dev/null +++ b/opentofu/provider.tf @@ -0,0 +1,78 @@ +terraform { + required_providers { + proxmox = { + source = "telmate/proxmox" + version = "3.0.2-rc04" + } + + bitwarden = { + source = "maxlaverse/bitwarden" + version = ">= 0.13.6" + } + } + + backend "pg" {} + encryption { + key_provider "pbkdf2" "mykey" { + passphrase = var.passphrase + key_length = 32 + salt_length = 16 + hash_function = "sha256" + } + method "aes_gcm" "secure_method" { + keys = key_provider.pbkdf2.mykey + } + state { + method = method.aes_gcm.secure_method + enforced = true + } + } +} + +variable "ci_user" { + type = string + sensitive = true +} + +variable "ci_password" { + type = string + sensitive = true +} + +variable "proxmox_api_url" { + type = string + sensitive = true +} + +variable "proxmox_api_token_id" { + type = string + sensitive = true +} + +variable "proxmox_api_token_secret" { + type = string + sensitive = true +} + +variable "ssh_key" { + type = string + sensitive = true +} + +variable "passphrase" { + type = string + sensitive = true +} + +provider "proxmox" { + pm_api_url = var.proxmox_api_url + pm_user = "root@pam" + pm_password = var.proxmox_api_token_secret + pm_timeout = 3600 + pm_parallel = 2 # Fix VM HDD lock timeout + # Optional: Skip TLS Verification + pm_tls_insecure = true + +} + +