Compare commits

28 Commits

Author SHA1 Message Date
max 130fcb5069 fix(taks): allows to use more complex passwords 2026-09-17 19:13:46 +04:00
max 93cee75103 fix(task): minor names and typos fixes 2026-09-17 17:59:08 +04:00
max 24f34198ff feat(pb,role,task): makes VM deployment playbook use new validation pre_tasks, become_method and os_template tasks are shared now 2026-09-17 14:29:23 +04:00
max f29de42397 feat(pb,role,task): generalize names of common pre_tasks and their variables, specifies LXC-specific ones 2026-09-17 11:16:18 +04:00
max daa5826573 feat(pb,role,task): completes the LXC deployment workflow using new validation tasks 2026-09-17 10:50:46 +04:00
max 0696218eb6 feat(pb,task): moves input validation into separated tasks files. Play fails immediately if a validation fails 2026-09-17 10:16:05 +04:00
max 4789bbd850 fix(pb): makes hostname validation strict enough 2026-09-16 17:51:49 +04:00
max e5e4aa1832 fix(role): omz installation 2026-09-16 17:25:09 +04:00
max 74f567c8eb fix(role): adds root and human_admin_user to sudoers in Alpine 2026-09-16 16:51:08 +04:00
max 996e242180 fix(role): enables qemu-guest-agent service only if the host is a VM 2026-09-16 09:44:01 +04:00
max 9627a4a5e0 feat(pb): removes TLS certificates after the distribution 2026-09-16 09:41:32 +04:00
max e20776a571 Merge branch 'main' into dev
Keep feature branch updated to avoid future issues
2026-09-16 09:08:38 +04:00
max 2fccf20768 fix(git): removes irrelevant data from issue templates
Deploy new config to the Ansible server / trigger-webhook (pull_request) Has been cancelled
2026-09-16 09:03:41 +04:00
max 130ad1dba5 feat(role): installs qemu-guest-agent on Alpine, enables and starts the service 2026-09-15 19:33:43 +04:00
max 6649426ca7 feat(pb): makes human admin user optional 2026-09-15 17:53:18 +04:00
max c7418ad40f feat(pb): adds IP address validation for LXC and VM deployment 2026-09-15 15:06:44 +05:00
max b3771097c2 feat(pb): adds default values for var prompts 2026-09-14 20:09:58 +04:00
max 2e1f8d4146 Merge pull request 'Update crt play' (#18) from update_crt_play into dev
Reviewed-on: #18
2026-09-14 17:00:42 +04:00
max 14e09c7ee7 fix(role): replaces ansible.builtin.fetch with ansible.posix.synchronize to pull the directory 2026-09-14 16:53:47 +04:00
max a5200c8d9b feat(git): adds issue templates
Deploy new config to the Ansible server / trigger-webhook (pull_request) Successful in 1m8s
2026-09-14 16:44:28 +05:00
max e59f07fb6d feat(pb,role): adds alpine as an option for VM deploying 2026-09-14 14:50:44 +04:00
max 6bef8b28e5 feat(role): ensures that sudo group exists 2026-09-14 14:50:44 +04:00
max eb7fd456e4 fix(role): replaces ssh with openssh, adds tzdata 2026-09-14 14:50:44 +04:00
max 5f4f84ea15 feat(role): sets locale for alpine correctly 2026-09-14 14:50:44 +04:00
max 8f4fee8b63 change(inv): moves localhost to hosts.yaml, removes dedicated localhost.yaml 2026-09-14 14:50:44 +04:00
max 1d3bc59a8b fix(playbook): play name in maintain playbook 2026-09-14 14:50:44 +04:00
max 49bd2f630b feat(role,pb,inv): new role for updating certificates on web-servers 2026-09-14 09:58:47 +04:00
max 07b33028d1 fix(playbook): play name in maintain playbook 2026-09-14 09:44:21 +04:00
45 changed files with 776 additions and 400 deletions
+97
View File
@@ -0,0 +1,97 @@
name: "Bug Report"
description: "Report a bug or unexpected behavior to help us improve"
title: "[BUG]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
## ⚠️ Before You Begin
Please ensure you have:
- [ ] Searched existing issues to avoid duplicates
- [ ] Confirmed this is a bug, not a question or configuration problem
- type: textarea
id: description
attributes:
label: "📋 Description"
description: "Provide a clear and concise description of the bug"
placeholder: "What happened? What did you expect to happen instead?"
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: "🔁 Steps to Reproduce"
description: "Step-by-step instructions to reproduce the issue"
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: "✅ Expected Behavior"
description: "What you expected to happen"
placeholder: "A clear description of what should happen..."
validations:
required: true
- type: textarea
id: actual
attributes:
label: "❌ Actual Behavior"
description: "What actually happened"
placeholder: "Include error messages, stack traces, or unexpected outcomes..."
validations:
required: true
- type: textarea
id: logs
attributes:
label: "📄 Logs / Screenshots"
description: |
Provide relevant logs, error messages, or screenshots.
For logs, please use a pastebin and share the URL.
**Remember to remove any sensitive information (API keys, passwords, etc.).**
placeholder: "Paste logs here or provide a Gist URL..."
validations:
required: false
- type: markdown
attributes:
value: |
---
## 🖥️ Environment Details
Please fill out the relevant information below.
- type: textarea
id: additional
attributes:
label: "📎 Additional Context"
description: "Any other information that might be relevant"
placeholder: |
- Database type and version (e.g., PostgreSQL 15, SQLite)
- Reverse proxy/CDN in use (e.g., Nginx, Cloudflare)
- Any custom configuration
- Related issues or PRs
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: "✅ Submission Checklist"
description: "Please confirm the following before submitting"
options:
- label: "I have searched for existing issues (open and closed) that report the same problem"
required: true
- label: "I have provided clear steps to reproduce the issue"
required: true
- label: "I have included relevant logs or error messages (with sensitive info removed)"
required: false
+118
View File
@@ -0,0 +1,118 @@
name: "Feature Request"
description: "Suggest a new feature or enhancement for this project"
title: "[FEATURE]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
## ⚠️ Before You Begin
Please ensure you have:
- [ ] Searched existing issues (open and closed) to avoid duplicates
- [ ] Reviewed the [project documentation](https://git.vmn.su/max/vmn-ansible/wiki) for existing functionality
- [ ] Confirmed this is a feature request, not a bug or configuration question
- type: textarea
id: problem
attributes:
label: "🎯 Problem Statement"
description: "What problem does this feature solve? What can't you do today?"
placeholder: |
I'm always frustrated when...
Currently, it's hard/impossible to...
This feature would help me because...
validations:
required: true
- type: textarea
id: solution
attributes:
label: "💡 Proposed Solution"
description: "Describe the solution you'd like to see"
placeholder: |
A clear description of what you want to happen.
If you have a specific API, UI, or implementation in mind, describe it here.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: "🔄 Alternatives Considered"
description: "What alternative solutions or workarounds have you considered?"
placeholder: |
- Alternative A: ...
- Workaround B: ...
- Why these don't fully solve the problem
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: "📸 Screenshots"
description: "If you can, provide screenshots or diagrams of the proposed feature"
placeholder: "Drag and drop images or paste links..."
validations:
required: false
- type: markdown
attributes:
value: |
---
## 🔧 Implementation Details (Optional)
The following sections are for contributors who want to implement this feature.
- type: textarea
id: technical
attributes:
label: "⚙️ Technical Approach"
description: "If you have ideas about how to implement this, share them here"
placeholder: |
- Which components/modules would be affected?
- Any database schema changes?
- API design considerations?
validations:
required: false
- type: textarea
id: acceptance
attributes:
label: "✅ Acceptance Criteria"
description: "What does 'done' look like for this feature?"
placeholder: |
- [ ] Feature works in the UI
- [ ] API endpoints are documented
- [ ] Tests are added
- [ ] Documentation is updated
validations:
required: false
- type: dropdown
id: contribution
attributes:
label: "🤝 Are you willing to contribute this feature?"
description: "Knowing if you can help implement this helps us prioritize"
options:
- "Yes, I can contribute"
- "Yes, but I need guidance"
- "No, but I can test/review"
- "No, I'm just requesting"
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "✅ Submission Checklist"
description: "Please confirm the following before submitting"
options:
- label: "I have searched for existing issues (open and closed) that request the same feature"
required: true
- label: "I have explained the problem this feature solves"
required: true
- label: "I have described a concrete solution or direction"
required: true
- label: "I have considered alternatives and explained why they fall short"
required: false
+145
View File
@@ -0,0 +1,145 @@
name: "Refactor Request"
description: "Propose a code restructuring or cleanup with no behavior change"
title: "[REFACTOR]: "
labels: ["refactoring"]
body:
- type: markdown
attributes:
value: |
## ⚠️ Before You Begin
Please ensure you have:
- [ ] Searched existing issues (open and closed) to avoid duplicates
- [ ] Reviewed the [project documentation](https://git.vmn.su/max/vmn-ansible/wiki) for architecture guidelines
- [ ] Confirmed this is a refactor (code restructuring with NO behavior change), not a bug fix or new feature
- type: textarea
id: problem
attributes:
label: "🎯 Problem Statement"
description: "What pain points does the current code cause? Why is this refactor needed?"
placeholder: |
The current implementation is difficult to maintain because...
This code has accumulated technical debt due to...
I'm constantly frustrated when working with this module because...
validations:
required: true
- type: textarea
id: scope
attributes:
label: "📂 Scope"
description: "Which files, modules, or components need refactoring? Be specific."
placeholder: |
- File paths: `src/auth/`, `internal/handler/`
- Component: `UserService` class
validations:
required: true
- type: textarea
id: current-state
attributes:
label: "🔍 Current State"
description: "Describe the current code structure, its issues, and why it's problematic"
placeholder: |
- The function `processUser()` is 500+ lines long
- Duplicated logic across 3 different files
- Mixed concerns (business logic + HTTP handling)
- Poor test coverage makes changes risky
- Naming is unclear and inconsistent
validations:
required: true
- type: textarea
id: proposed-changes
attributes:
label: "💡 Proposed Solution"
description: "What should the code look like after the refactor?"
placeholder: |
- Extract `processUser()` into smaller, focused functions
- Consolidate duplicated logic into a shared utility
- Separate business logic from HTTP handlers
- Introduce interfaces for better testability
- Rename variables and functions for clarity
validations:
required: true
- type: textarea
id: constraints
attributes:
label: "🚫 Constraints"
description: "What must NOT change? Public APIs, behavior, backward compatibility, performance characteristics?"
placeholder: |
- All public APIs must remain identical
- Database schema must not change
- External behavior must be identical
- Response formats must stay the same
- Performance must not degrade
validations:
required: true
- type: textarea
id: benefits
attributes:
label: "📈 Expected Benefits"
description: "What improvements will this refactor bring?"
placeholder: |
- Improved maintainability
- Better testability
- Reduced code duplication
- Clearer separation of concerns
- Easier onboarding for new contributors
validations:
required: false
- type: textarea
id: risks
attributes:
label: "⚠️ Risks & Mitigations"
description: "What could go wrong, and how will you mitigate it?"
placeholder: |
- Risk: Regression bugs
Mitigation: Comprehensive test coverage before and after
- Risk: Large diff making code review difficult
Mitigation: Break into smaller, incremental PRs
validations:
required: false
- type: textarea
id: alternatives
attributes:
label: "🔄 Alternatives Considered"
description: "What other approaches did you consider, and why did you reject them?"
placeholder: |
- Alternative A: Complete rewrite — rejected because too risky
- Alternative B: Gradual deprecation — rejected because too slow
validations:
required: false
- type: dropdown
id: contribution
attributes:
label: "🤝 Are you willing to contribute this refactor?"
description: "Knowing if you can help implement this helps us prioritize"
options:
- "Yes, I can contribute"
- "Yes, but I need guidance"
- "No, but I can review"
- "No, I'm just suggesting"
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: "✅ Submission Checklist"
description: "Please confirm the following before submitting"
options:
- label: "I have searched for existing issues (open and closed) that request the same refactor"
required: true
- label: "I have clearly explained the problem this refactor solves"
required: true
- label: "I have described the scope of the refactor (specific files/modules)"
required: false
- label: "I have identified what must NOT change (APIs, behavior, backward compatibility)"
required: false
+19 -4
View File
@@ -1,11 +1,18 @@
---
all:
children:
this_host:
hosts:
localhost:
ansible_become_password: "{{ ansible_become_passwd }}"
ansible_connection: local
ansible_python_interpreter: "{{ ansible_playbook_python }}"
physical:
proxmox_1:
ansible_host: "{{ proxmox_1_host }}"
ansible_become_password: "{{ ansible_become_passwd }}"
services_to_run: []
hosts:
proxmox_1:
ansible_host: "{{ proxmox_1_host }}"
ansible_become_password: "{{ ansible_become_passwd }}"
services_to_run: []
virtual:
hosts:
localhost:
@@ -39,3 +46,11 @@ all:
ansible_become_password: "{{ ansible_become_passwd }}"
services_to_run:
- coder_ai
web-servers:
hosts:
cert-proxy:
ansible_host: 192.168.0.47
ansible_become_password: "{{ ansible_become_passwd }}"
monitoring:
ansible_host: 192.168.0.49
ansible_become_password: "{{ ansible_become_passwd }}"
-7
View File
@@ -1,7 +0,0 @@
---
physical:
hosts:
localhost:
ansible_become_password: "{{ ansible_become_passwd }}"
ansible_connection: local
ansible_python_interpreter: "{{ ansible_playbook_python }}"
+25 -37
View File
@@ -1,6 +1,6 @@
---
- name: Deploy LXC
hosts: all
hosts: localhost
remote_user: ansible
gather_facts: false
@@ -10,50 +10,30 @@
ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}"
user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}"
vars_prompt:
- name: lxc_hostname
prompt: "Hostname for the container (may only contain letters (a-z, A-Z), numbers (0-9), and hyphens)"
private: false
pre_tasks:
- name: Include input and validation for hostname
ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml
- name: Include input and validation for LXC root password
ansible.builtin.include_tasks: ../tasks/lxc_root_password_validation.yaml
- name: lxc_root_password
prompt: "Root password"
private: true
confirm: true
encrypt: sha512_crypt
- name: Include input and validation for IP
ansible.builtin.include_tasks: ../tasks/ip_validation.yaml
- name: lxc_ip_address
prompt: "IP address for the container (in x.x.x.x/x formant)"
private: false
- name: Include input and validation for LXC privilege
ansible.builtin.include_tasks: ../tasks/lxc_privileged_validation.yaml
- name: lxc_is_privileged
prompt: "Unprivileged LXC (true/false)?"
private: false
- name: Include input and validation for human admin user creation
ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml
pre_tasks:
- name: Validate hostname
ansible.builtin.fail:
msg: |
Invalid hostname: "{{ lxc_hostname }}"
Hostname may only contain letters (a-z, A-Z), numbers (0-9), and hyphens (-).
when: not lxc_hostname is regex('^[a-zA-Z0-9-]+$')
run_once: true
roles:
- ../roles/deploy_lxc_on_proxmox
tasks:
- name: Make the prompted hostname available to the whole playbook
ansible.builtin.set_fact:
fact_lxc_hostname: "{{ lxc_hostname }}"
- name: Make the prompted IP available to the whole playbook
ansible.builtin.set_fact:
#fact_lxc_ip_address: "{{ lxc_ip_address | ansible.utils.ipaddr('address') }}"
fact_lxc_ip_address: "{{ lxc_ip_address.split('/') | first }}"
- name: Add the target host to the inventory
ansible.builtin.add_host:
name: "{{ fact_lxc_ip_address }}"
name: "{{ guest_ip_address }}"
groups: new_host
ansible_user: ansible
ansible_become_password: "{{ ansible_become_passwd }}"
@@ -73,7 +53,15 @@
- ../roles/configure_ansible_user
- ../roles/harden_ssh
- ../roles/base_system
- ../roles/human_admin_user
# Update configs - omz, nvim, ranger and so on. Distribute the last version of those configs
- ../roles/update_configs
tasks:
- name: Include human admin user role
ansible.builtin.include_role:
name: ../roles/human_admin_user
when: "hostvars['localhost']['create_human_admin_user'] | bool"
- name: Include update configs role
ansible.builtin.include_role:
name: ../roles/update_configs
when: "hostvars['localhost']['create_human_admin_user'] | bool"
+28 -29
View File
@@ -1,6 +1,6 @@
---
- name: Deploy VM
hosts: all
hosts: localhost
remote_user: ansible
gather_facts: false
@@ -10,42 +10,33 @@
ansible_user_passwd_hash: "{{ ansible_password | password_hash('sha512', 's3edscrj45e6r') }}"
user_passwd_hash: "{{ user_password | password_hash('sha512', 's3ed6123jhgcr') }}"
vars_prompt:
- name: vm_hostname
prompt: "Hostname for the VM (may only contain letters (a-z, A-Z), numbers (0-9), and hyphens)"
private: false
- name: vm_ip_address
prompt: "IP address for the VM (in x.x.x.x/x formant)"
private: false
pre_tasks:
- name: Validate hostname
ansible.builtin.fail:
msg: |
Invalid hostname: "{{ vm_hostname }}"
Hostname may only contain letters (a-z, A-Z), numbers (0-9), and hyphens (-).
when: not vm_hostname is regex('^[a-zA-Z0-9-]+$')
run_once: true
- name: Include input and validation for hostname
ansible.builtin.include_tasks: ../tasks/hostname_validation.yaml
- name: Include input and validation for IP
ansible.builtin.include_tasks: ../tasks/ip_validation.yaml
- name: Include input and validation for human admin user creation
ansible.builtin.include_tasks: ../tasks/human_admin_user_validation.yaml
- name: Include input and validation for OS template
ansible.builtin.include_tasks: ../tasks/os_template_validation.yaml
- name: Include input and validation for become method
ansible.builtin.include_tasks: ../tasks/become_method_validation.yaml
roles:
- ../roles/deploy_vm_on_proxmox
tasks:
- name: Make the prompted hostname available to the whole playbook
ansible.builtin.set_fact:
fact_vm_hostname: "{{ vm_hostname }}"
- name: Make the prompted IP available to the whole playbook
ansible.builtin.set_fact:
fact_vm_ip_address: "{{ vm_ip_address.split('/') | first }}"
- name: Add the target host to the inventory
ansible.builtin.add_host:
name: "{{ fact_vm_ip_address }}"
name: "{{ guest_ip_address }}"
groups: new_host
ansible_user: ansible
ansible_become_password: "{{ ansible_become_passwd }}"
ansible_become_method: "{{ guest_become_method }}"
- name: Configure VM
@@ -62,7 +53,15 @@
- ../roles/configure_ansible_user
- ../roles/harden_ssh
- ../roles/base_system
- ../roles/human_admin_user
# Update configs - omz, nvim, ranger and so on. Distribute the last version of those configs
- ../roles/update_configs
tasks:
- name: Include human admin user role
ansible.builtin.include_role:
name: ../roles/human_admin_user
when: "hostvars['localhost']['create_human_admin_user'] | bool"
- name: Include update configs role
ansible.builtin.include_role:
name: ../roles/update_configs
when: "hostvars['localhost']['create_human_admin_user'] | bool"
+28 -1
View File
@@ -1,5 +1,5 @@
---
- name: Deploy and set up an LXC container in Proxmox
- name: Perform regular maintenance
hosts: virtual
remote_user: ansible
@@ -20,3 +20,30 @@
- ../roles/update_configs
# Make sure that the services that are supposed to run are running
- ../roles/check_services
- name: Ensure all web-servers have fresh TLS certificates
hosts: web-servers
remote_user: ansible
vars_files:
../inventory/group_vars/all/secrets.yaml
roles:
# Pull certificates from cert_proxy and distribute them among the servers
- ../roles/update_certificates
post_tasks:
- name: Delete certificates from cert-proxy
ansible.builtin.file:
name: /tmp/letsencrypt
state: absent
when: "inventory_hostname == 'cert-proxy'"
run_once: true
- name: Delete certificates from ansible-server
ansible.builtin.file:
name: /tmp/letsencrypt
state: absent
run_once: true
delegate_to: 127.0.0.1
+23 -1
View File
@@ -7,7 +7,7 @@
become: true
remote_user: ansible
- name: Install qemu-guest-agent on VM
- name: Install qemu-guest-agent on VM with apt-based systems
when:
- ansible_facts['os_family'] == "Debian"
- ansible_facts['virtualization_type'] == "kvm"
@@ -29,3 +29,25 @@
name: "{{ alpine_packages }}"
update_cache: true
remote_user: ansible
- name: Install qemu-guest-agent on VM with Alpine
when:
- ansible_facts['distribution'] == "Alpine"
- ansible_facts['virtualization_type'] == "kvm"
community.general.apk:
name: qemu-guest-agent
update_cache: true
become: true
remote_user: ansible
tags:
- kvm-guests
- packages
- name: Enable qemu-guest-agent
ansible.builtin.service:
name: qemu-guest-agent
enabled: yes
state: started
when: ansible_facts['virtualization_type'] == "kvm"
@@ -1,19 +1,63 @@
---
- name: Generate locales
- name: Generate locales for Debian-based systems
community.general.locale_gen:
name:
- en_US.UTF-8
- ru_RU.UTF-8
state: present
when: (ansible_facts['distribution'] == "Debian") or
(ansible_facts['distribution'] == "Ubuntu")
- name: Set locale
- name: Set locale for Debian-based systems
ansible.builtin.copy:
dest: /etc/locale.conf
mode: '0644'
content: |
LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8
when: (ansible_facts['distribution'] == "Debian") or
(ansible_facts['distribution'] == "Ubuntu")
# The same for Alpine
- name: Install musl-locales package for Alpine
community.general.apk:
name: musl-locales
state: present
when: ansible_facts['distribution'] == "Alpine"
- name: Copy default locale profile to a custom override file for Alpine
ansible.builtin.copy:
src: /etc/profile.d/20locale.sh
dest: /etc/profile.d/locale.sh
remote_src: true
mode: '0644'
owner: root
group: root
force: false # Do not overwrite if the file already exists
when: ansible_facts['distribution'] == "Alpine"
- name: Set LANG and LC_ALL in the custom locale file for Alpine
ansible.builtin.lineinfile:
path: /etc/profile.d/locale.sh
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
loop:
- { regexp: '^export LANG=', line: 'export LANG=en_US.UTF-8' }
- { regexp: '^export LC_ALL=', line: 'export LC_ALL=en_US.UTF-8' }
when: ansible_facts['distribution'] == "Alpine"
- name: Ensure Unicode is enabled in /etc/rc.conf for Alpine
ansible.builtin.lineinfile:
path: /etc/rc.conf
regexp: '^#?unicode='
line: 'unicode="YES"'
state: present
when: ansible_facts['distribution'] == "Alpine"
# Works fine for all
- name: Set time
community.general.timezone:
name: Europe/Samara
@@ -6,8 +6,9 @@ alpine_packages:
- git
- kitty
- neovim
- openssh
- openssh-server
- ranger
- rsync
- ssh
- tzdata
- zsh
+28 -1
View File
@@ -8,14 +8,35 @@
- sudo
update-cache: true
- name: Ensure /etc/resolv.conf exists with DNS servers
when: (ansible_facts['distribution'] == "Alpine")
ansible.builtin.copy:
dest: /etc/resolv.conf
content: |
nameserver 192.168.0.1
nameserver 8.8.8.8
mode: '0644'
owner: root
group: root
become: true
# The same commands for Alpine
- name: Update and install packages on Alpine
- name: Install sudo on Alpine
when: (ansible_facts['distribution'] == "Alpine")
community.general.apk:
name: sudo
update_cache: true
remote_user: ansible
- name: Enable sudo for root and human_admin_user
when: (ansible_facts['distribution'] == "Alpine")
ansible.builtin.copy:
dest: /etc/sudoers.d/80-ansible-created-users
content: |
root ALL=(ALL) ALL:ALL
{{ human_admin_user }} ALL=(ALL) ALL:ALL
remote_user: ansible
## Creating and setting up the ansible user
## First, create sshusers group to grant ssh access
@@ -24,6 +45,12 @@
name: sshusers
state: present
- name: Ensure group "sudo" exists
ansible.builtin.group:
name: sudo
state: present
## Add the user to sshusers (for ssh access) and sudo (gain root access)
- name: Create a new user with a password for Ansible
ansible.builtin.user:
+4 -4
View File
@@ -12,9 +12,9 @@
cmode: "shell"
password: "{{ lxc_root_password }}"
hostname: "{{ lxc_hostname }}"
hostname: "{{ guest_hostname }}"
ostemplate: 'main:vztmpl/debian-13-golden-image.tar.gz'
unprivileged: "{{ lxc_is_privileged }}"
unprivileged: "{{ lxc_privileged }}"
memory: 2048
cores: 2
state: present
@@ -26,7 +26,7 @@
storage: local
pubkey: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/ansible_key.pub') }}"
netif:
net0: "name=eth0,gw=192.168.0.1,ip={{ lxc_ip_address }},bridge=vmbr0"
net0: "name=eth0,gw=192.168.0.1,ip={{ guest_ip_address }}/{{ guest_netmask }},bridge=vmbr0"
- name: Make sure the container has started
become: false
@@ -38,7 +38,7 @@
api_token_id: ansible
api_token_secret: "{{ proxmox_token_secret }}"
hostname: "{{ lxc_hostname }}"
hostname: "{{ guest_hostname }}"
state: started
retries: 3
delay: 15
@@ -10,8 +10,8 @@
api_token_id: ansible
api_token_secret: "{{ proxmox_token_secret }}"
clone: debian-13-cloud-init-template
name: "{{ vm_hostname }}"
clone: "{{ guest_os_template }}-cloud-init-template"
name: "{{ guest_hostname }}"
storage: main
format: qcow2
full: true
@@ -33,7 +33,7 @@
api_token_id: ansible
api_token_secret: "{{ proxmox_token_secret }}"
name: "{{ vm_hostname }}"
name: "{{ guest_hostname }}"
# Cloud-init parameters
update: true
ide:
@@ -41,7 +41,7 @@
ciuser: "ansible"
cipassword: "{{ ansible_become_passwd }}"
ipconfig:
ipconfig0: "ip={{ vm_ip_address }},gw=192.168.0.1"
ipconfig0: "ip={{ guest_ip_address }}/{{ guest_netmask }},gw=192.168.0.1"
- name: Tweak the hardware settings
become: false
@@ -53,7 +53,7 @@
api_token_id: ansible
api_token_secret: "{{ proxmox_token_secret }}"
name: "{{ vm_hostname }}"
name: "{{ guest_hostname }}"
cores: 4
memory: 4096 # Minimal value for debian based OS for smooth workflow
update: true
@@ -68,5 +68,5 @@
api_token_id: ansible
api_token_secret: "{{ proxmox_token_secret }}"
name: "{{ vm_hostname }}"
name: "{{ guest_hostname }}"
state: started
+1 -1
View File
@@ -9,7 +9,7 @@
api_token_id: ansible
api_token_secret: "{{ proxmox_token_secret }}"
name: "{{ vm_hostname }}"
name: "{{ guest_hostname }}"
state: current
ignore_errors: true
register: vm_status
@@ -10,7 +10,7 @@
api_token_id: ansible
api_token_secret: "{{ proxmox_token_secret }}"
name: "{{ vm_hostname }}"
name: "{{ guest_hostname }}"
state: started
- name: Sleep for 120 seconds to ensure that Proxmox has cloned the VM
+7 -5
View File
@@ -1,15 +1,17 @@
#!/bin/sh
FILE=/home/$USER/.oh-my-zsh/oh-my-zsh.sh
TARGET_USER=$1
FILE=/home/$TARGET_USER/.oh-my-zsh/oh-my-zsh.sh
if [ -f "$FILE" ]; then
echo "$FILE exists and we not installing ohmyzsh"
exit 0
else
echo "$FILE does not exist and we install ohmyzsh"
cd /home/max
wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
chmod +x /home/max/install.sh
timeout -s 15 -k 30s 20s /home/max/install.sh --unattendend --keep-zshrc || exit 0
cd /home/$TARGET_USER
sudo -u $TARGET_USER wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
chmod +x /home/$TARGET_USER/install.sh
sudo -u $TARGET_USER timeout -s 15 -k 30s 20s /home/$TARGET_USER/install.sh --unattendend --keep-zshrc || exit 0
exit 0
fi
+2 -3
View File
@@ -25,10 +25,9 @@
# WARNING: UNPRIVILEGED USER (not ansible) COMMANDS
- name: Install oh my zsh
remote_user: "{{ human_admin_user }}"
ansible.builtin.command: "/home/{{ human_admin_user }}/install_omz.sh"
remote_user: ansible
ansible.builtin.command: "/home/{{ human_admin_user }}/install_omz.sh {{ human_admin_user }}"
changed_when: true
vars:
- name: Configure oh my zsh, by pushing the config file
remote_user: ansible
@@ -1 +0,0 @@
max:$apr1$NPIFdkB/$hya7NW2rdTGEKBDm0PH3g0
@@ -1 +0,0 @@
max:$apr1$/3oBTCwW$u9OMKSD56v8czs5rZ0rDp0
@@ -1,27 +0,0 @@
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
@@ -1,57 +0,0 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param REMOTE_USER $remote_user;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
# !!! Security workaround !!!
# Do not use HTTP_HOST as "$http_host".
#
# "$http_host" is the Host header exactly as supplied by the client.
# This is unsafe when a client sends an absolute-form request target together
# with a different Host header, for example:
#
# GET https://example.com/ HTTP/1.1
# Host: malformedhost
#
# In such a case, passing "$http_host" upstream exposes the raw client-supplied
# Host value ("malformedhost") to the backend application, even though it does
# not match the effective request target. Applications often use HTTP_HOST for
# redirects, absolute URL generation, virtual host routing, or security checks;
# forwarding the raw Host header can therefore lead to incorrect or unsafe
# behaviour.
#
# Newer nginx versions (since 1.30.0) introduce variables "$is_request_port" and
# "$request_port", allowing HTTP_HOST to be constructed as:
# $host$is_request_port$request_port
#
# In stable/oldstable packages we use "$host" as a security workaround.
# It avoids forwarding an untrusted raw Host header to the backend.
#
# Note: this changes behaviour compared to previous versions, because "$host"
# does not preserve the client-supplied port, while "$http_host" typically
# does. Existing deployments that rely on "$http_host" containing a port number
# may therefore break or behave differently after this change.
fastcgi_param HTTP_HOST $host;
-101
View File
@@ -1,101 +0,0 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xslt+xml xsl xslt;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/ogg ogv;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-matroska mkv;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-http-auth-pam.conf
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-http-dav-ext.conf
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-http-echo.conf
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-http-geoip2.conf
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-http-subs-filter.conf
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-http-upstream-fair.conf
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-stream.conf
@@ -1 +0,0 @@
/usr/share/nginx/modules-available/mod-stream-geoip2.conf
@@ -1,24 +0,0 @@
user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
include /etc/nginx/conf.d/*.conf;
# Hide NGINX version from responses
server_tokens off;
# Use modern TLS protocols only
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
}
@@ -1,33 +0,0 @@
# !!! Security workaround !!!
# Do not set the `Host` header as "$http_host".
#
# "$http_host" is the Host header exactly as supplied by the client.
# This is unsafe when a client sends an absolute-form request target together
# with a different Host header, for example:
#
# GET https://example.com/ HTTP/1.1
# Host: malformedhost
#
# In such a case, passing "$http_host" upstream exposes the raw client-supplied
# Host value ("malformedhost") to the backend application, even though it does
# not match the effective request target. Applications often use HTTP_HOST for
# redirects, absolute URL generation, virtual host routing, or security checks;
# forwarding the raw Host header can therefore lead to incorrect or unsafe
# behaviour.
#
# Newer nginx versions (since 1.30.0) introduce variables "$is_request_port" and
# "$request_port", allowing `Host` to be constructed as:
# $host$is_request_port$request_port
#
# In stable/oldstable packages we use "$host" as a security workaround.
# It avoids forwarding an untrusted raw Host header to the backend.
#
# Note: this changes behaviour compared to previous versions, because "$host"
# does not preserve the client-supplied port, while "$http_host" typically
# does. Existing deployments that rely on "$http_host" containing a port number
# may therefore break or behave differently after this change.
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
@@ -1 +0,0 @@
/srv/http/default/snippets/error_pages.conf
@@ -1 +0,0 @@
/srv/http/default/snippets/error_pages_content.conf
@@ -1,13 +0,0 @@
# regex to split $uri to $fastcgi_script_name and $fastcgi_path
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# Check that the PHP script exists before passing it
try_files $fastcgi_script_name =404;
# Bypass the fact that try_files resets $fastcgi_path_info
# see: http://trac.nginx.org/nginx/ticket/321
set $path_info $fastcgi_path_info;
fastcgi_param PATH_INFO $path_info;
fastcgi_index index.php;
include fastcgi.conf;
-30
View File
@@ -1,30 +0,0 @@
---
- name: Install NGINX on apt-based systems
become: true
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Enable systemd service
ansible.builtin.service:
name: nginx
state: started
enabled: true
# We don't want to activate this config right away, so push it after service started
- name: Configure NGINX, by pushing the config directory
become: true
ansible.builtin.copy:
src: "{{ role_path }}/files/nginx"
dst: "/etc/nginx"
- name: Install custom error pages
become: true
ansible.builtin.shell: |
mkdir -p /srv/http/default
git clone https://github.com/denysvitali/nginx-error-pages /srv/http/default
ln -s /srv/http/default/snippets/error_pages.conf /etc/nginx/snippets/error_pages.conf
ln -s /srv/http/default/snippets/error_pages_content.conf /etc/nginx/snippets/error_pages_content.conf
+23
View File
@@ -0,0 +1,23 @@
---
- name: Pull directory from remote host to control node
ansible.posix.synchronize:
mode: pull
src: /tmp/letsencrypt
dest: /tmp
recursive: true
delete: true
when: "inventory_hostname == 'cert-proxy'"
- name: Push loki.vmn.su certificate to the target
ansible.builtin.copy:
src: /tmp/letsencrypt/live/loki.vmn.su/fullchain.pem
dest: /etc/nginx/certs/loki_fullchain.pem
local_follow: true
when: "inventory_hostname == 'monitoring'"
- name: Push loki.vmn.su key to the target
ansible.builtin.copy:
src: /tmp/letsencrypt/live/loki.vmn.su/privkey.pem
dest: /etc/nginx/certs/loki_privkey.pem
local_follow: true
when: "inventory_hostname == 'monitoring'"
+22
View File
@@ -0,0 +1,22 @@
---
- name: Set default become method
ansible.builtin.set_fact:
guest_become_method: "sudo"
- name: Prompt for become method with immediate validation
ansible.builtin.pause:
prompt: "Choose become method, available: sudo, su, pbrun, pfexec, doas, dzdo, ksu, runas, machinectl [sudo]"
register: become_method_result
until: >
become_method_result.user_input | length == 0 or
(
become_method_result.user_input in ['sudo', 'su', 'pbrun', 'pfexec', 'doas', 'dzdo', 'ksu', 'runas', 'machinectl']
)
retries: 2
delay: 0
- name: Use user input only if provided, otherwise keep the default
ansible.builtin.set_fact:
guest_become_method: "{{ become_method_result.user_input }}"
when: become_method_result.user_input | length > 0
+23
View File
@@ -0,0 +1,23 @@
---
- name: Set default hostname
ansible.builtin.set_fact:
guest_hostname: "test-guest"
- name: Prompt for hostname with immediate validation
ansible.builtin.pause:
prompt: "Enter hostname (letters, digits, hyphens (but not leading and trailing), dots; max 253 chars) [{{ guest_hostname }}]"
register: hostname_result
until: >
hostname_result.user_input | length == 0 or
(
hostname_result.user_input | length <= 253 and
hostname_result.user_input is regex('^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$')
)
retries: 2
delay: 0
- name: Use user input only if provided, otherwise keep the default
ansible.builtin.set_fact:
guest_hostname: "{{ hostname_result.user_input }}"
when: hostname_result.user_input | length > 0
+22
View File
@@ -0,0 +1,22 @@
---
- name: Set default human admin user
ansible.builtin.set_fact:
create_human_admin_user: true
- name: Prompt for human admin user creation with immediate validation
ansible.builtin.pause:
prompt: "Create human admin user? (true/false) [true]"
register: human_admin_user_result
until: >
human_admin_user_result.user_input | length == 0 or
(
human_admin_user_result.user_input in ['true', 'false']
)
retries: 2
delay: 0
- name: Use user input only if provided, otherwise keep the default
ansible.builtin.set_fact:
create_human_admin_user: "{{ human_admin_user_result.user_input }}"
when: human_admin_user_result.user_input | length > 0
+29
View File
@@ -0,0 +1,29 @@
---
- name: Set default ip
ansible.builtin.set_fact:
guest_ip_address: "192.168.0.90"
- name: Set default network mask
ansible.builtin.set_fact:
guest_netmask: "24"
- name: Prompt for IP with immediate validation
ansible.builtin.pause:
prompt: "Enter ip in x.x.x.x/x format [192.168.0.90/24]"
register: ip_result
until: >
ip_result.user_input | length == 0 or
ip_result.user_input is regex('^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)/(3[0-2]|[12]?\\d)$')
retries: 2
delay: 0
- name: Use user input only if provided, otherwise keep the default (IP)
ansible.builtin.set_fact:
guest_ip_address: "{{ ip_result.user_input.split('/') | first }}"
when: ip_result.user_input | length > 0
- name: Use user input only if provided, otherwise keep the default (netmask)
ansible.builtin.set_fact:
guest_netmask: "{{ ip_result.user_input.split('/') | second }}"
when: ip_result.user_input | length > 0
+22
View File
@@ -0,0 +1,22 @@
---
- name: Set default privileged
ansible.builtin.set_fact:
lxc_privileged: false
- name: Prompt for privileged with immediate validation
ansible.builtin.pause:
prompt: "Privileged LXC? (true/false) [false]"
register: privileged_result
until: >
privileged_result.user_input | length == 0 or
(
privileged_result.user_input in ['true', 'false']
)
retries: 2
delay: 0
- name: Use user input only if provided, otherwise keep the default
ansible.builtin.set_fact:
lxc_privileged: "{{ privileged_result.user_input }}"
when: privileged_result.user_input | length > 0
+33
View File
@@ -0,0 +1,33 @@
---
- name: Set default password
ansible.builtin.set_fact:
lxc_root_password: "test-guest"
- name: Prompt for password
ansible.builtin.pause:
prompt: "Enter your password [{{ lxc_root_password }}]"
echo: false
register: password_result_1
until: password_result_1.user_input | length == 0
retries: 2
delay: 0
- name: Prompt for password (confirmation)
ansible.builtin.pause:
prompt: "Confirm your new password"
echo: false
register: password_result_2
- name: Ensure passwords match
ansible.builtin.assert:
that:
- password_result_1.user_input == password_result_2.user_input
fail_msg: "Passwords do not match!"
success_msg: "Password confirmed."
- name: Use user input only if provided, otherwise keep the default
ansible.builtin.set_fact:
lxc_root_password: "{{ password_result_1.user_input }}"
when: password_result_1.user_input | length > 0
+22
View File
@@ -0,0 +1,22 @@
---
- name: Set default OS template
ansible.builtin.set_fact:
guest_os_template: "debian-13"
- name: Prompt for OS template with immediate validation
ansible.builtin.pause:
prompt: "Choose OS template, available: debian-13, alpine [debian-13]"
register: os_template_result
until: >
os_template_result.user_input | length == 0 or
(
os_template_result.user_input in ['debian-13', 'alpine']
)
retries: 2
delay: 0
- name: Use user input only if provided, otherwise keep the default
ansible.builtin.set_fact:
guest_os_template: "{{ os_template_result.user_input }}"
when: os_template_result.user_input | length > 0