Initial Commit
This commit is contained in:
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
@ -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/
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -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.
|
26
README.md
Normal file
26
README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|

|
||||||
|
|
||||||
|
## 📖 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`
|
||||||
|
|
||||||
|
|
2
ansible/.gitignore
vendored
Normal file
2
ansible/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
roles/
|
||||||
|
.env
|
21
ansible/ansible.cfg
Normal file
21
ansible/ansible.cfg
Normal file
@ -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
|
||||||
|
|
||||||
|
|
24
ansible/ghshr.yml
Normal file
24
ansible/ghshr.yml
Normal file
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
14
ansible/group_vars/all.yml
Normal file
14
ansible/group_vars/all.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
install_packages:
|
||||||
|
- apt-transport-https
|
||||||
|
- ca-certificates
|
||||||
|
- curl
|
||||||
|
- gnupg
|
||||||
|
- lsb-release
|
||||||
|
- vim
|
||||||
|
- git
|
||||||
|
- htop
|
||||||
|
- jq
|
||||||
|
- mc
|
||||||
|
- net-tools
|
||||||
|
- unattended-upgrades
|
3
ansible/inventory/hosts.ini
Normal file
3
ansible/inventory/hosts.ini
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[ghshr]
|
||||||
|
ghshr.comprofix.xyz
|
||||||
|
|
3
ansible/main.yml
Normal file
3
ansible/main.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
- import_playbook: ghshr.yml
|
||||||
|
|
53
ansible/tasks/base.yml
Normal file
53
ansible/tasks/base.yml
Normal file
@ -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
|
17
ansible/tasks/pull_image.yml
Executable file
17
ansible/tasks/pull_image.yml
Executable file
@ -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 }) }}
|
40
opentofu/200-github.tf
Normal file
40
opentofu/200-github.tf
Normal file
@ -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 = <<EOF
|
||||||
|
${var.ssh_key}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
memory = "4096"
|
||||||
|
swap = "512"
|
||||||
|
|
||||||
|
rootfs {
|
||||||
|
storage = "local-zfs"
|
||||||
|
size = "8G"
|
||||||
|
}
|
||||||
|
|
||||||
|
features {
|
||||||
|
fuse = true
|
||||||
|
nesting = true
|
||||||
|
mount = "nfs;cifs"
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
name = "eth0"
|
||||||
|
bridge = "vmbr0"
|
||||||
|
ip = "10.10.10.8/24"
|
||||||
|
gw = "10.10.10.1"
|
||||||
|
tag = 10
|
||||||
|
}
|
||||||
|
}
|
34
opentofu/prepareEnv.sh
Executable file
34
opentofu/prepareEnv.sh
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
bw config server https://vault.comprofix.com
|
||||||
|
bw login
|
||||||
|
export BW_SESSION=$(bw unlock --raw)
|
||||||
|
bw sync
|
||||||
|
|
||||||
|
echo "Please wait while we prepare terraform.auto.tfvars"
|
||||||
|
|
||||||
|
proxmox_api_url=$(bw get --session $BW_SESSION uri proxmox_api)
|
||||||
|
proxmox_api_token_id=$(bw get --session $BW_SESSION username f295a859-154a-482d-8129-c6ec6e06131e)
|
||||||
|
proxmox_api_token_secret=$(bw get --session $BW_SESSION password f295a859-154a-482d-8129-c6ec6e06131e)
|
||||||
|
ci_user=$(bw get --session $BW_SESSION username ci_details)
|
||||||
|
ci_password=$(bw get --session $BW_SESSION password ci_details)
|
||||||
|
ssh_key=$(bw get --session $BW_SESSION notes ssh_public_key_main)
|
||||||
|
passphrase=$(bw get --session $BW_SESSION password state_passphrase)
|
||||||
|
tfusername=$(bw get --session $BW_SESSION username tofu_postgres)
|
||||||
|
tfpassword=$(bw get --session $BW_SESSION password tofu_postgres)
|
||||||
|
tfurl=$(bw get --session $BW_SESSION uri tofu_postgres)
|
||||||
|
|
||||||
|
echo 'proxmox_api_url = "'$proxmox_api_url'"' > 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"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
78
opentofu/provider.tf
Normal file
78
opentofu/provider.tf
Normal file
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user