diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 0000000..afceea6 --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,37 @@ +--- +profile: basic + +# Paths to exclude from linting +exclude_paths: + - .ansible/ + - collections/ansible_collections/ + - roles/geerlingguy.java/ + - roles/oatakan.rhel_ovirt_template/ + - roles/oatakan.rhel_template_build/ + - roles/oatakan.windows_template_build/ + - roles/oatakan.windows_update/ + - roles/oatakan.windows_virtio/ + - roles/ikke_t.container_image_cleanup/ + - roles/ikke_t.podman_container_systemd/ + - roles/sage905.mineos/ + - roles/sage905.waterfall/ + +# Warn rather than fail on these during initial adoption +warn_list: + - yaml[line-length] + - name[casing] + - fqcn[action-core] + - no-changed-when + +# Rules to skip entirely during initial adoption +skip_list: + - role-name # toal-common doesn't follow FQCN yet + +# Use progressive mode: only flag new violations on changed files +# (useful for gradual adoption in existing projects) +# progressive: true + +mock_modules: + - community.general.proxmox_kvm + +mock_roles: [] diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..e99cba1 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(du:*)" + ] + } +} diff --git a/.devcontainer/podman/devcontainer.json b/.devcontainer/podman/devcontainer.json index ebafd9f..e52a86b 100644 --- a/.devcontainer/podman/devcontainer.json +++ b/.devcontainer/podman/devcontainer.json @@ -33,5 +33,6 @@ "mounts": [ "source=${localEnv:XDG_RUNTIME_DIR}/containers/auth.json,target=/container-auth.json,type=bind,consistency=cached", "source=${localEnv:HOME}/Dev/inventories/toallab-inventory,target=/workspaces/inventory,type=bind,consistency=cached", + "source=${localEnv:HOME}/Dev/ansible_collections/,target=/workspaces/collections/,type=bind,consistency=cached", ] } diff --git a/.gitignore b/.gitignore index 2c025ac..a333f83 100644 --- a/.gitignore +++ b/.gitignore @@ -107,7 +107,14 @@ venv.bak/ # Ansible *.retry +ansible-navigator.log +.ansible/ +# Vendor roles (install via roles/requirements.yml) +roles/geerlingguy.* +roles/oatakan.* +roles/ikke_t.* +roles/sage905.* .vscode/ keys/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c13cfa9..7b1578f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,3 +3,26 @@ repos: rev: v8.18.2 hooks: - id: gitleaks + + - repo: https://github.com/adrienverge/yamllint + rev: v1.35.1 + hooks: + - id: yamllint + args: [--config-file, .yamllint] + exclude: | + (?x)^( + roles/geerlingguy\..*/| + roles/oatakan\..*/| + roles/ikke_t\..*/| + roles/sage905\..*/| + \.ansible/| + collections/ansible_collections/ + ) + + - repo: https://github.com/ansible/ansible-lint + rev: v25.1.3 + hooks: + - id: ansible-lint + # ansible-lint reads .ansible-lint for configuration + additional_dependencies: + - ansible-core>=2.15 diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..fe8e302 --- /dev/null +++ b/.yamllint @@ -0,0 +1,39 @@ +--- +extends: default + +rules: + # Allow longer lines for readability in tasks + line-length: + max: 160 + level: warning + + # Allow both true/false and yes/no boolean styles + truthy: + allowed-values: ['true', 'false', 'yes', 'no'] + check-keys: false + + # Ansible uses double-bracket Jinja2 - allow in strings + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 + + # Allow some indentation flexibility for Ansible block style + indentation: + spaces: 2 + indent-sequences: true + check-multi-line-strings: false + + # Comments should have a space after # + comments: + min-spaces-from-content: 1 + + # Don't require document-start marker on every file + document-start: disable + +ignore: | + roles/geerlingguy.* + roles/oatakan.* + roles/ikke_t.* + roles/sage905.* + .ansible/ + collections/ansible_collections/ diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..d0f5940 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,44 @@ +[defaults] +# Inventory - override with -i or ANSIBLE_INVENTORY env var +inventory = /workspaces/inventory + +# Role and collection paths +roles_path = roles +collections_path = /workspaces/collections:~/.ansible/collections + +# Interpreter discovery +interpreter_python = auto_silent + +# Performance +gathering = smart +fact_caching = jsonfile +fact_caching_connection = /tmp/ansible_fact_cache +fact_caching_timeout = 3600 + +# Output +stdout_callback = yaml +bin_ansible_callbacks = True +callbacks_enabled = profile_tasks + +# SSH settings +host_key_checking = False +timeout = 30 + +# Vault +vault_password_file = vault-id-from-op-client.sh + +# Misc +retry_files_enabled = False +nocows = True + +[inventory] +# Enable inventory plugins +enable_plugins = host_list, yaml, ini, auto, toml + +[privilege_escalation] +become = False +become_method = sudo + +[ssh_connection] +pipelining = True +ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no diff --git a/library/parse_opnsense_leases.py b/library/parse_opnsense_leases.py deleted file mode 100755 index 25b36fd..0000000 --- a/library/parse_opnsense_leases.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/python -import ipaddress -import yaml -import os -from collections import defaultdict - -def load_yaml_input(file_path): - with open(file_path, "r") as f: - return yaml.safe_load(f) - -def group_by_subnet(data, mask=24): - grouped = defaultdict(list) - - for record in data: - ip_str = record.get("address") - if not ip_str: - continue - try: - ip = ipaddress.ip_address(ip_str) - network = ipaddress.ip_network(f"{ip}/{mask}", strict=False) - grouped[str(network)].append(record) - except ValueError: - print(f"Skipping invalid IP: {ip_str}") - - return grouped - -def save_groups_to_yaml(grouped, output_dir="."): - os.makedirs(output_dir, exist_ok=True) - - for subnet, entries in grouped.items(): - safe_subnet = subnet.replace("/", "_") - filename = os.path.join(output_dir, f"subnet_{safe_subnet}.yaml") - with open(filename, "w") as f: - yaml.dump(entries, f, default_flow_style=False) - print(f"Saved {len(entries)} entries to {filename}") - -def main(): - input_file = "input.yaml" # change as needed - output_dir = "output_subnets" - cidr_mask = 24 # change to desired subnet size - - records = load_yaml_input(input_file) - grouped = group_by_subnet(records, cidr_mask) - save_groups_to_yaml(grouped, output_dir) - -if __name__ == "__main__": - main() diff --git a/playbooks/build_ansible.yml b/playbooks/build_ansible.yml deleted file mode 100644 index 6d4a714..0000000 --- a/playbooks/build_ansible.yml +++ /dev/null @@ -1,59 +0,0 @@ ---- -- name: VM Provisioning - hosts: tag_ansible:&tag_tower - connection: local - collections: - - redhat.rhv - - tasks: - - block: - - name: Obtain SSO token from username / password credentials - ovirt_auth: - url: "{{ ovirt_url }}" - username: "{{ ovirt_username }}" - password: "{{ ovirt_password }}" - - - name: Disks Created - ovirt_disk: - auth: "{{ ovirt_auth }}" - description: "Boot Disk for {{ inventory_hostname }}" - interface: virtio - size: 120GiB - storage_domain: nas_iscsi - bootable: True - wait: true - name: "{{ inventory_hostname }}_disk0" - state: present - - - name: VM Created - ovirt_vm: - - - - - name: Add NIC to VM - ovirt_nic: - state: present - vm: - name: mynic - interface: e1000 - mac_address: 00:1a:4a:16:01:56 - profile: ovirtmgmt - network: ovirtmgmt - - - name: Plug NIC to VM - redhat.rhv.ovirt_nic: - state: plugged - vm: myvm - name: mynic - - - always: - - name: Always revoke the SSO token - ovirt_auth: - state: absent - ovirt_auth: "{{ ovirt_auth }}" - - -# - name: VM Configuration -# - name: Automation Platform Installer -# - name: diff --git a/playbooks/build_windows_template.yml b/playbooks/build_windows_template.yml deleted file mode 100644 index 50c5655..0000000 --- a/playbooks/build_windows_template.yml +++ /dev/null @@ -1,12 +0,0 @@ -- name: Create an ovirt windows template - hosts: windows_template_base - gather_facts: false - connection: local - become: false - - vars: - ansible_python_interpreter: "{{ ansible_playbook_python }}" - - - roles: - - oatakan.windows_ovirt_template diff --git a/playbooks/create_gitea.yml b/playbooks/create_gitea.yml new file mode 100644 index 0000000..d79b336 --- /dev/null +++ b/playbooks/create_gitea.yml @@ -0,0 +1,31 @@ +--- +- name: Create Gitea Server + hosts: gitea + gather_facts: false + + vars: + dnsmadeeasy_hostname: "{{ service_dns_name.split('.') | first }}" + dnsmadeeasy_domain: "{{ service_dns_name.split('.',1) |last }}" + dnsmadeeasy_record_type: CNAME + dnsmadeeasy_record_value: gate.toal.ca. + dnsmadeeasy_record_ttl: 600 + opnsense_service_hostname: "{{ dnsmadeeasy_hostname }}" + opnsense_service_domain: "{{ dnsmadeeasy_domain }}" + + tasks: + - name: Configure DNS + ansible.builtin.import_role: + name: toallab.infra.dnsmadeeasy + tasks_from: provision.yml + + - name: Configure Service + ansible.builtin.import_role: + name: toallab.infra.opnsense_service + tasks_from: provision.yml + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ opnsense_host }}" + api_key: "{{ opnsense_api_key }}" + api_secret: "{{ opnsense_api_secret }}" + ssl_verify: "{{ opnsense_ssl_verify }}" + api_port: "{{ opnsense_api_port|default(omit) }}" diff --git a/playbooks/deploy_openshift.yml b/playbooks/deploy_openshift.yml new file mode 100644 index 0000000..6ade77b --- /dev/null +++ b/playbooks/deploy_openshift.yml @@ -0,0 +1,6 @@ +- name: Deploy OpenShift on Proxmox + hosts: all + gather_facts: false + connection: local + tasks: + diff --git a/playbooks/opnsense.yml b/playbooks/opnsense.yml index 88b2ccd..f9a2e16 100644 --- a/playbooks/opnsense.yml +++ b/playbooks/opnsense.yml @@ -11,7 +11,18 @@ api_port: "{{ opnsense_api_port|default(omit) }}" tasks: - # TODO: Clean up subnet / reservation structure + - name: Install packages + ansibleguy.opnsense.package: + name: + - os-acme-client + action: install + delegate_to: localhost + + - name: Setup ACME Client + ansible.builtin.include_role: + name: toallab.infra.opnsense_service + tasks_from: setup.yml + - name: Configure KEA DHCP Server ansibleguy.opnsense.dhcp_general: enabled: "{{ dhcp_enabled }}" @@ -51,5 +62,3 @@ reload: false delegate_to: localhost loop: "{{ all_dhcp_reservations }}" - - - name: Add HAProxy \ No newline at end of file diff --git a/playbooks/output.csv b/playbooks/output.csv deleted file mode 100644 index 751ae52..0000000 --- a/playbooks/output.csv +++ /dev/null @@ -1 +0,0 @@ -hostname, domain, description, enabled, mx, mxprio, prio, record_type, server, value, uuid diff --git a/vault-id-from-op-client.sh b/vault-id-from-op-client.sh new file mode 100755 index 0000000..9ecb8ca --- /dev/null +++ b/vault-id-from-op-client.sh @@ -0,0 +1,36 @@ +#!/bin/bash + + +# Parse input arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --vault-id) + VAULT_ID="$2" + shift 2 + ;; + *) + echo "Usage: $0 --vault-id " >&2 + exit 1 + ;; + esac +done + +# Validate vault ID +if [[ -z "$VAULT_ID" ]]; then + echo "Error: Missing required --vault-id argument" >&2 + exit 1 +fi + +ITEM_NAME="${VAULT_ID} vault key" +FIELD_NAME="password" + +# Fetch the vault password from 1Password +VAULT_PASSWORD=$(op item get "$ITEM_NAME" --fields "$FIELD_NAME" --format=json --vault LabSecrets 2>/dev/null | jq -r '.value') + +# Output the password or report error +if [[ -n "$VAULT_PASSWORD" && "$VAULT_PASSWORD" != "null" ]]; then + echo "$VAULT_PASSWORD" +else + echo "Error: Could not retrieve vault password for vault ID '$VAULT_ID' (item: '$ITEM_NAME')" >&2 + exit 1 +fi