diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f336e7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Vault password file - NEVER commit this! +.ansible-vault-pass +~/.ansible-vault-pass + +# Temporary files +*.tmp +*.bak +*~ + +# Python bytecode +__pycache__/ +*.py[cod] +*$py.class + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index 1f5386a..b005bc9 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,28 @@ This Ansible playbook automates the setup of development environments across mul ansible-galaxy collection install -r collections/requirements.yml ``` +### Vault Password Setup +Host variables are encrypted with Ansible Vault. You have two options: + +#### Option 1: Vault Password File (Recommended) +Create a vault password file: +```bash +# Create the vault password file +echo "your_vault_password" > ~/.ansible-vault-pass +chmod 600 ~/.ansible-vault-pass +``` + +#### Option 2: Interactive Password Prompt +Use `--ask-vault-pass` with each command to be prompted for the vault password. + ### Basic Setup ```bash -# Run on all development machines +# Run on all development machines (with vault password file) ansible-playbook dev-playbook.yml +# Run on all development machines (interactive vault password) +ansible-playbook dev-playbook.yml --ask-vault-pass + # Run on specific host ansible-playbook dev-playbook.yml --limit devVM @@ -68,6 +85,20 @@ Add `skip_reboot=true` to host variables: bottom ansible_host=10.0.10.156 ansible_user=beast skip_reboot=true ``` +### Debug Output +Control debug information display with the `ansible_debug_output` variable: + +```bash +# Default: No debug output (clean, production-ready output) +ansible-playbook dev-playbook.yml --limit devVM + +# Enable debug output (shows detailed status information) +ansible-playbook dev-playbook.yml --limit devVM -e "ansible_debug_output=true" + +# Set permanently in group_vars/all.yml +ansible_debug_output: true +``` + ### Dry Run ```bash # Check what would be changed @@ -82,6 +113,7 @@ ansible-playbook dev-playbook.yml -v ### Global Variables (`group_vars/all.yml`) - `timezone`: System timezone (default: UTC) - `locale`: System locale (default: en_US.UTF-8) +- `ansible_debug_output`: Show debug information (default: false) - `fail2ban_bantime`: Ban duration in seconds - `fail2ban_findtime`: Time window for failures - `fail2ban_maxretry`: Max failures before ban diff --git a/ansible.cfg b/ansible.cfg index 53a244e..60b5ca8 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -4,8 +4,10 @@ host_key_checking = True timeout = 30 gathering = smart fact_caching = memory -stdout_callback = yaml +stdout_callback = default +deprecation_warnings = False +vault_password_file = ~/.ansible-vault-pass [ssh_connection] -ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o IdentitiesOnly=yes +ssh_args = -o ControlMaster=auto -o ControlPersist=60s pipelining = True diff --git a/group_vars/all.yml b/group_vars/all.yml index ac27f2e..874ab69 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -4,6 +4,9 @@ timezone: UTC locale: en_US.UTF-8 ansible_python_interpreter: /usr/bin/python3 +# Debug settings +ansible_debug_output: false + # Security settings fail2ban_bantime: 3600 fail2ban_findtime: 600 diff --git a/hosts b/hosts index c1b5a9b..a106925 100644 --- a/hosts +++ b/hosts @@ -14,3 +14,6 @@ debianDesktopVM ansible_host=10.0.10.206 ansible_user=user skip_reboot=true [ansible] ansible-controlVM ansible_host=localhost ansible_user=master + +[local] +localhost ansible_connection=local diff --git a/local-playbook.yml b/local-playbook.yml new file mode 100644 index 0000000..1dbd461 --- /dev/null +++ b/local-playbook.yml @@ -0,0 +1,24 @@ +- hosts: localhost + connection: local + become: true + + roles: + - { role: maintenance, tags: ['maintenance'] } + - { role: base, tags: ['base', 'security'] } + - { role: user, tags: ['user'] } + - { role: ssh, tags: ['ssh', 'security'] } + - { role: shell, tags: ['shell'] } + - { role: development, tags: ['development', 'dev'] } + - { role: docker, tags: ['docker'] } + - { role: applications, tags: ['applications', 'apps'] } + - { role: snap, tags: ['snap', 'apps'] } + + pre_tasks: + - name: Update apt cache + apt: + update_cache: yes + + tasks: + - name: Display completion message + debug: + msg: "Local development environment setup completed successfully!" diff --git a/roles/applications/defaults/main.yml b/roles/applications/defaults/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/applications/defaults/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/applications/handlers/main.yml b/roles/applications/handlers/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/applications/handlers/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/applications/tasks/main.yml b/roles/applications/tasks/main.yml index 066b896..3302853 100644 --- a/roles/applications/tasks/main.yml +++ b/roles/applications/tasks/main.yml @@ -1,13 +1,20 @@ --- -- name: Check if desktop applications are installed - apt: - list: "{{ item }}" - register: app_check - loop: - - redshift - - libreoffice - - evince +- name: Check if applications are already installed + package_facts: + manager: apt + +- name: Check if Brave browser is installed + command: brave-browser --version + register: brave_check + ignore_errors: true changed_when: false + failed_when: false + no_log: true + +- name: Set installation conditions + set_fact: + desktop_apps_needed: "{{ ['redshift', 'libreoffice', 'evince'] | difference(ansible_facts.packages.keys()) | length > 0 }}" + brave_needs_install: "{{ brave_check.rc != 0 or 'brave-browser' not in ansible_facts.packages }}" - name: Install desktop applications apt: @@ -16,63 +23,44 @@ - libreoffice - evince state: present - when: - - app_check.results[0].installed is not defined or - - app_check.results[1].installed is not defined or - - app_check.results[2].installed is not defined + when: desktop_apps_needed -- name: Check if Brave is already installed - command: brave-browser --version - register: brave_check - ignore_errors: true - changed_when: false +- name: Brave browser installation + block: + - name: Remove old Brave repository files + file: + path: "{{ item }}" + state: absent + loop: + - /etc/apt/sources.list.d/brave-browser.list + - /etc/apt/sources.list.d/brave-browser-release.sources -- name: Check if Brave package is installed via apt - apt: - list: brave-browser - register: brave_apt_check - changed_when: false + - name: Download Brave APT key + get_url: + url: https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg + dest: /usr/share/keyrings/brave-browser-archive-keyring.gpg + mode: '0644' -- name: Remove old Brave repository files - file: - path: "{{ item }}" - state: absent - loop: - - /etc/apt/sources.list.d/brave-browser.list - - /etc/apt/sources.list.d/brave-browser-release.sources - when: brave_check.rc != 0 or brave_apt_check.results[0].installed is not defined + - name: Add Brave repository + apt_repository: + repo: "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main" + filename: brave-browser + state: present -- name: Download Brave APT key - get_url: - url: https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg - dest: /usr/share/keyrings/brave-browser-archive-keyring.gpg - mode: '0644' - when: brave_check.rc != 0 or brave_apt_check.results[0].installed is not defined + - name: Install Brave browser + apt: + name: brave-browser + state: present -- name: Add Brave repo (all Debian family) - apt_repository: - repo: "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main" - filename: brave-browser - state: present - when: brave_check.rc != 0 or brave_apt_check.results[0].installed is not defined - -- name: Update apt cache after Brave repo add - apt: - update_cache: yes - when: brave_check.rc != 0 or brave_apt_check.results[0].installed is not defined - -- name: Install Brave browser - apt: - name: brave-browser - state: present - when: brave_check.rc != 0 or brave_apt_check.results[0].installed is not defined + when: brave_needs_install - name: Display application status debug: msg: - - "Redshift installed: {{ 'Yes' if app_check.results[0].installed is defined else 'No' }}" - - "LibreOffice installed: {{ 'Yes' if app_check.results[1].installed is defined else 'No' }}" - - "Evince installed: {{ 'Yes' if app_check.results[2].installed is defined else 'No' }}" - - "Brave already installed: {{ brave_check.stdout if brave_check.rc == 0 else 'Not found' }}" - - "Brave package installed: {{ 'Yes' if brave_apt_check.results[0].installed is defined else 'No' }}" - - "Actions taken: {{ 'None - All apps already present' if app_check.results[0].installed is defined and app_check.results[1].installed is defined and app_check.results[2].installed is defined and brave_check.rc == 0 and brave_apt_check.results[0].installed is defined else 'Some applications installed/updated' }}" + - "Desktop apps needed: {{ desktop_apps_needed }}" + - "Brave needed: {{ brave_needs_install }}" + - "Redshift: {{ 'Installed' if 'redshift' in ansible_facts.packages else 'Missing' }}" + - "LibreOffice: {{ 'Installed' if 'libreoffice' in ansible_facts.packages else 'Missing' }}" + - "Evince: {{ 'Installed' if 'evince' in ansible_facts.packages else 'Missing' }}" + - "Brave: {{ brave_check.stdout if brave_check.rc == 0 else 'Not installed' }}" + when: ansible_debug_output | default(false) | bool \ No newline at end of file diff --git a/roles/base/tasks/main.yml b/roles/base/tasks/main.yml index 3826f97..4f50c3e 100644 --- a/roles/base/tasks/main.yml +++ b/roles/base/tasks/main.yml @@ -1,27 +1,19 @@ --- -- name: Install base packages +- name: Install base system packages apt: name: + # Base utilities - htop - curl - wget - unzip - xclip - state: present - update_cache: yes - -- name: Install admin tools - apt: - name: + # Network and admin tools - net-tools - ufw - fail2ban - mailutils - state: present - -- name: Install monitoring tools - apt: - name: + # Monitoring tools - iotop - nethogs - logwatch @@ -43,5 +35,4 @@ - name: Configure locale locale_gen: name: "{{ locale | default('en_US.UTF-8') }}" - state: present - + state: present \ No newline at end of file diff --git a/roles/development/tasks/main.yml b/roles/development/tasks/main.yml index 7b19d6b..44c320c 100644 --- a/roles/development/tasks/main.yml +++ b/roles/development/tasks/main.yml @@ -1,21 +1,13 @@ --- -- name: Install dev packages +- name: Install development packages apt: name: + # Development tools - git - nodejs - state: present - -- name: Install build tools - apt: - name: + - npm + # Build tools - build-essential - python3 - python3-pip - state: present - -- name: Install npm - apt: - name: npm - state: present - + state: present \ No newline at end of file diff --git a/roles/docker/tasks/main.yml b/roles/docker/tasks/main.yml index 620bdf8..c354c34 100644 --- a/roles/docker/tasks/main.yml +++ b/roles/docker/tasks/main.yml @@ -6,112 +6,90 @@ - "Distribution Release: {{ ansible_facts['distribution_release'] }}" - "Distribution Version: {{ ansible_facts['distribution_version'] }}" - "OS Family: {{ ansible_facts['os_family'] }}" + when: ansible_debug_output | default(false) | bool - name: Check if Docker is already installed command: docker --version register: docker_check ignore_errors: true changed_when: false + failed_when: false + no_log: true - name: Check if Docker packages are installed via apt - apt: - list: docker-ce + package_facts: + manager: apt register: docker_apt_check changed_when: false -- name: Install Docker requirements - apt: - name: - - apt-transport-https - - ca-certificates - - curl - - gnupg - - lsb-release - state: present - update_cache: yes +- name: Set installation condition + set_fact: + docker_needs_install: "{{ docker_check.rc != 0 or 'docker-ce' not in ansible_facts.packages }}" -- name: Remove old Docker repository files - file: - path: "{{ item }}" - state: absent - loop: - - /etc/apt/sources.list.d/docker.list - - /etc/apt/sources.list.d/docker-ce.list - when: docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined +- name: Docker installation tasks + block: + - name: Install Docker requirements + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + state: present -- name: Create keyrings directory - file: - path: /etc/apt/keyrings - state: directory - mode: '0755' - when: docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Remove old Docker repository files + file: + path: "{{ item }}" + state: absent + loop: + - /etc/apt/sources.list.d/docker.list + - /etc/apt/sources.list.d/docker-ce.list -- name: Download Docker's official GPG key - get_url: - url: https://download.docker.com/linux/ubuntu/gpg - dest: /etc/apt/keyrings/docker.gpg - mode: '0644' - when: docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Create keyrings directory + file: + path: /etc/apt/keyrings + state: directory + mode: '0755' -- name: Add Docker repository for Ubuntu - apt_repository: - repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" - state: present - when: - - ansible_facts['distribution'] == "Ubuntu" - - docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Setup Docker GPG key + include_tasks: setup_gpg_key.yml -- name: Add Docker repository for Debian - apt_repository: - repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable" - state: present - when: - - ansible_facts['distribution'] == "Debian" - - docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Setup Docker repository + include_tasks: "setup_repo_{{ ansible_facts['distribution'] | lower | replace(' ', '_') }}.yml" -- name: Add Docker repository for Linux Mint - apt_repository: - repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" - state: present - when: - - ansible_facts['distribution'] == "Linux Mint" - - docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Install Docker CE + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present -- name: Update apt cache after Docker repo add - apt: - update_cache: yes - when: docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Start and enable Docker service + systemd: + name: docker + state: started + enabled: yes -- name: Install Docker CE - apt: - name: - - docker-ce - - docker-ce-cli - - containerd.io - - docker-buildx-plugin - - docker-compose-plugin - state: present - update_cache: yes - when: docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Set target user variable + set_fact: + target_user: "{{ ansible_user | default(ansible_user_id) }}" -- name: Start and enable Docker service - systemd: - name: docker - state: started - enabled: yes - when: docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + - name: Add user to docker group + user: + name: "{{ target_user }}" + groups: docker + append: yes -- name: Add user to docker group - user: - name: "{{ ansible_user }}" - groups: docker - append: yes - when: docker_check.rc != 0 or docker_apt_check.results[0].installed is not defined + when: docker_needs_install - name: Display Docker status debug: msg: - "Docker already installed: {{ docker_check.stdout if docker_check.rc == 0 else 'Not found' }}" - - "Docker CE package installed: {{ 'Yes' if docker_apt_check.results[0].installed is defined else 'No' }}" - - "Actions taken: {{ 'None - Docker already present' if docker_check.rc == 0 and docker_apt_check.results[0].installed is defined else 'Docker installation/configuration performed' }}" - + - "Docker CE package installed: {{ 'Yes' if 'docker-ce' in ansible_facts.packages else 'No' }}" + - "Actions taken: {{ 'None - Docker already present' if not docker_needs_install else 'Docker installation/configuration performed' }}" + when: ansible_debug_output | default(false) | bool \ No newline at end of file diff --git a/roles/docker/tasks/setup_gpg_key.yml b/roles/docker/tasks/setup_gpg_key.yml new file mode 100644 index 0000000..0a72cb3 --- /dev/null +++ b/roles/docker/tasks/setup_gpg_key.yml @@ -0,0 +1,19 @@ +--- +- name: Download Docker's official GPG key + get_url: + url: https://download.docker.com/linux/ubuntu/gpg + dest: /tmp/docker.gpg + mode: '0644' + +- name: Convert and install Docker GPG key + shell: gpg --dearmor < /tmp/docker.gpg > /etc/apt/keyrings/docker.gpg + +- name: Set permissions on Docker GPG key + file: + path: /etc/apt/keyrings/docker.gpg + mode: '0644' + +- name: Clean up temporary GPG key file + file: + path: /tmp/docker.gpg + state: absent diff --git a/roles/docker/tasks/setup_repo_debian.yml b/roles/docker/tasks/setup_repo_debian.yml new file mode 100644 index 0000000..9624a2c --- /dev/null +++ b/roles/docker/tasks/setup_repo_debian.yml @@ -0,0 +1,6 @@ +--- +- name: Add Docker repository for Debian + apt_repository: + repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable" + state: present + update_cache: yes diff --git a/roles/docker/tasks/setup_repo_linux_mint.yml b/roles/docker/tasks/setup_repo_linux_mint.yml new file mode 100644 index 0000000..37f2753 --- /dev/null +++ b/roles/docker/tasks/setup_repo_linux_mint.yml @@ -0,0 +1,10 @@ +--- +- name: Set Ubuntu codename for Linux Mint + set_fact: + ubuntu_codename: "{{ 'jammy' if ansible_distribution_version is version('22', '>=') else 'focal' if ansible_distribution_version is version('21', '>=') else 'focal' if ansible_distribution_version is version('20', '>=') else 'bionic' }}" + +- name: Add Docker repository for Linux Mint (using Ubuntu base) + apt_repository: + repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ubuntu_codename }} stable" + state: present + update_cache: yes diff --git a/roles/docker/tasks/setup_repo_ubuntu.yml b/roles/docker/tasks/setup_repo_ubuntu.yml new file mode 100644 index 0000000..45cef72 --- /dev/null +++ b/roles/docker/tasks/setup_repo_ubuntu.yml @@ -0,0 +1,6 @@ +--- +- name: Add Docker repository for Ubuntu + apt_repository: + repo: "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" + state: present + update_cache: yes diff --git a/roles/maintenance/handlers/main.yml b/roles/maintenance/handlers/main.yml index bde5993..ed97d53 100644 --- a/roles/maintenance/handlers/main.yml +++ b/roles/maintenance/handlers/main.yml @@ -1,11 +1 @@ --- -# handlers file for maintenance - -- name: restart fail2ban - systemd: - name: fail2ban - state: restarted - -- name: reload ufw - ufw: - state: reloaded diff --git a/roles/maintenance/tasks/main.yml b/roles/maintenance/tasks/main.yml index cebd7bf..cd3c86c 100644 --- a/roles/maintenance/tasks/main.yml +++ b/roles/maintenance/tasks/main.yml @@ -1,8 +1,4 @@ --- -- name: Update apt cache - apt: - update_cache: yes - - name: Upgrade all packages apt: upgrade: dist diff --git a/roles/shell/tasks/main.yml b/roles/shell/tasks/main.yml index 0017921..75dcd60 100644 --- a/roles/shell/tasks/main.yml +++ b/roles/shell/tasks/main.yml @@ -1,4 +1,8 @@ --- +- name: Set target user variable + set_fact: + target_user: "{{ ansible_user | default(ansible_user_id) }}" + - name: Install shell packages apt: name: @@ -8,29 +12,29 @@ - name: Set zsh as default shell for user user: - name: "{{ ansible_user }}" + name: "{{ target_user }}" shell: /usr/bin/zsh - name: Install Oh My Zsh for user become: true - become_user: "{{ ansible_user }}" + become_user: "{{ target_user }}" shell: sh -c "$(wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O -)" "" --unattended args: - creates: "/home/{{ ansible_user }}/.oh-my-zsh" + creates: "/home/{{ target_user }}/.oh-my-zsh" - name: Clone Powerlevel10k theme git: repo: https://github.com/romkatv/powerlevel10k.git - dest: "/home/{{ ansible_user }}/.oh-my-zsh/custom/themes/powerlevel10k" + dest: "/home/{{ target_user }}/.oh-my-zsh/custom/themes/powerlevel10k" depth: 1 update: no become: true - become_user: "{{ ansible_user }}" + become_user: "{{ target_user }}" - name: Deploy .zshrc for user copy: src: files/.zshrc - dest: "/home/{{ ansible_user }}/.zshrc" - owner: "{{ ansible_user }}" - group: "{{ ansible_user }}" + dest: "/home/{{ target_user }}/.zshrc" + owner: "{{ target_user }}" + group: "{{ target_user }}" mode: '0644' diff --git a/roles/snap/defaults/main.yml b/roles/snap/defaults/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/roles/snap/defaults/main.yml @@ -0,0 +1 @@ +--- diff --git a/roles/ssh/tasks/main.yml b/roles/ssh/tasks/main.yml index d3c7818..1a6234b 100644 --- a/roles/ssh/tasks/main.yml +++ b/roles/ssh/tasks/main.yml @@ -4,10 +4,17 @@ name: openssh-server state: present -- name: Configure firewalls +- name: Configure firewalls - allow SSH port + ufw: + rule: allow + port: '22' + proto: tcp + +- name: Configure firewalls - allow SSH by name (backup) ufw: rule: allow name: OpenSSH + ignore_errors: true - name: Enable UFW with deny default policy ufw: diff --git a/roles/user/tasks/main.yml b/roles/user/tasks/main.yml index f16e674..47bcd88 100644 --- a/roles/user/tasks/main.yml +++ b/roles/user/tasks/main.yml @@ -1,5 +1,10 @@ --- +- name: Set target user variable + set_fact: + target_user: "{{ ansible_user | default(ansible_user_id) }}" + - name: Ensure user exists user: - name: "{{ ansible_user }}" + name: "{{ target_user }}" state: present + when: ansible_connection != 'local'