Archive Legacy Setup And Add Ansible Control Plane

This commit is contained in:
Dmitry
2026-07-08 12:47:37 +03:00
parent 841d56380b
commit 4bafa7d09e
203 changed files with 1663 additions and 11 deletions
View File
@@ -0,0 +1,7 @@
---
bash_config_file: /etc/profile.d/homelab.sh
bash_config_bashrc: /etc/bash.bashrc
bash_config_legacy_user: ada
bash_config_legacy_home: "/home/{{ bash_config_legacy_user }}"
bash_config_legacy_profile_files:
- /etc/profile.d/custom_bash.sh
+63
View File
@@ -0,0 +1,63 @@
- name: Install unified HomeLab bash config
ansible.builtin.template:
src: homelab.sh.j2
dest: "{{ bash_config_file }}"
owner: root
group: root
mode: "0644"
- name: Ensure system bashrc sources managed config
ansible.builtin.blockinfile:
path: "{{ bash_config_bashrc }}"
create: true
owner: root
group: root
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED HOMELAB BASH CONFIG"
block: |
if [ -f /etc/profile.d/homelab.sh ]; then
. /etc/profile.d/homelab.sh
fi
- name: List bash users with local home directories
ansible.builtin.shell: >-
getent passwd | awk -F: '$7 ~ /\/bash$/ && $6 ~ /^\// { print $1 ":" $6 }'
register: bash_config_bash_users_raw
changed_when: false
- name: Ensure bash users source managed system config
ansible.builtin.blockinfile:
path: "{{ item.split(':')[1] }}/.bashrc"
create: true
owner: "{{ item.split(':')[0] }}"
group: "{{ item.split(':')[0] }}"
mode: "0644"
marker: "# {mark} ANSIBLE MANAGED HOMELAB SYSTEM BASH CONFIG"
block: |
if [ -f /etc/profile.d/homelab.sh ]; then
. /etc/profile.d/homelab.sh
fi
loop: "{{ bash_config_bash_users_raw.stdout_lines }}"
- name: Check legacy per-user bashrc
ansible.builtin.stat:
path: "{{ bash_config_legacy_home }}/.bashrc"
register: bash_config_legacy_bashrc
- name: Remove legacy per-user bashrc source block
ansible.builtin.blockinfile:
path: "{{ bash_config_legacy_home }}/.bashrc"
marker: "# {mark} ANSIBLE MANAGED HOMELAB BASH CONFIG"
state: absent
when: bash_config_legacy_bashrc.stat.exists
- name: Remove legacy per-user bash config file
ansible.builtin.file:
path: "{{ bash_config_legacy_home }}/.bashrc.d/homelab.sh"
state: absent
- name: Remove legacy profile scripts that conflict with unified config
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop: "{{ bash_config_legacy_profile_files }}"
@@ -0,0 +1,44 @@
# Managed by Ansible: HomeLab unified bash config.
export EDITOR="${EDITOR:-vim}"
export PAGER="${PAGER:-less}"
export LESS="${LESS:--FRSX}"
export HISTCONTROL="ignoreboth:erasedups"
export HISTSIZE=50000
export HISTFILESIZE=100000
shopt -s histappend 2>/dev/null || true
shopt -s checkwinsize 2>/dev/null || true
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias grep='grep --color=auto'
alias ip='ip --color=auto'
alias df='df -h'
alias du='du -h'
alias free='free -h'
alias ..='cd ..'
alias ...='cd ../..'
alias please='sudo $(history -p !!)'
alias gs='git status --short --branch'
alias gd='git diff'
alias gl='git log --oneline --decorate -10'
if command -v systemctl >/dev/null 2>&1; then
alias sctl='systemctl'
alias jctl='journalctl'
fi
if [ -n "$PS1" ]; then
if [ "$(id -u)" -eq 0 ]; then
PS1='\[\e[31m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]# '
else
PS1='\[\e[32m\]\u@\h\[\e[0m\]:\[\e[34m\]\w\[\e[0m\]\$ '
fi
fi
@@ -1,5 +0,0 @@
bootstrap_user: ada
bootstrap_ssh_public_keys: [ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWNsUQmppB6cQccXX1ZaBbFIcmM6RmghsTVbG9TgoZB ada@ada-x1]
docker_registry_mirrors:
- https://mirror.gcr.io
- https://dockerhub.timeweb.cloud
@@ -1,4 +0,0 @@
- name: restart sshd
ansible.builtin.service:
name: sshd
state: restarted
-85
View File
@@ -1,85 +0,0 @@
- name: Ensure user exists
ansible.builtin.user:
name: "{{ bootstrap_user }}"
shell: /bin/bash
groups: sudo
append: true
create_home: true
state: present
# docker group создаётся только после установки Docker.
# Добавление происходит в роли docker, не здесь.
- name: Add SSH authorized keys
ansible.posix.authorized_key:
user: "{{ bootstrap_user }}"
key: "{{ item }}"
state: present
loop: "{{ bootstrap_ssh_public_keys }}"
when: bootstrap_ssh_public_keys | length > 0
- name: Gather service facts
ansible.builtin.service_facts:
- name: Ensure Docker config directory exists
ansible.builtin.file:
path: /etc/docker
state: directory
mode: "0755"
- name: Configure Docker registry mirrors
ansible.builtin.copy:
dest: /etc/docker/daemon.json
content: "{{ {'registry-mirrors': docker_registry_mirrors} | to_nice_json }}\n"
mode: "0644"
register: docker_daemon_config
when: docker_registry_mirrors | length > 0
- name: Restart Docker after daemon config change
ansible.builtin.service:
name: docker
state: restarted
when:
- docker_daemon_config is changed
- "'docker.service' in ansible_facts.services"
# Команды, которые реально нужны без пароля:
# apt / apt-get — обновление пакетов
# systemctl — управление сервисами
# ufw — фаервол
# resticprofile — установка systemd-таймеров бэкапа
#
# Ansible become НЕ входит в этот список намеренно:
# для плейбуков запускать с -K. Это сознательный компромисс
# между удобством и тем, чтобы не давать python3 NOPASSWD (= root).
- name: Configure sudoers — NOPASSWD for specific commands
ansible.builtin.copy:
dest: /etc/sudoers.d/{{ bootstrap_user }}
content: |
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/bin/apt, /usr/bin/apt-get
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/bin/ufw
{{ bootstrap_user }} ALL=(ALL) NOPASSWD: /usr/local/bin/resticprofile
validate: /usr/sbin/visudo -cf %s
mode: "0440"
# Drop-in конфиг — не трогаем основной sshd_config.
# Требует Debian 12+ (OpenSSH 8.9+).
- name: Harden SSH
ansible.builtin.copy:
dest: /etc/ssh/sshd_config.d/99-hardening.conf
content: |
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
mode: "0644"
notify: restart sshd
- name: Ensure sshd_config.d is included
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
line: Include /etc/ssh/sshd_config.d/*.conf
state: present
insertbefore: BOF
validate: /usr/sbin/sshd -t -f %s
notify: restart sshd
@@ -0,0 +1,22 @@
---
openvpn_interface: tun0
openvpn_listen_port: 8443
openvpn_proto_server: tcp-server
openvpn_proto_client: tcp-client
openvpn_config_dir: /etc/openvpn/homelab
openvpn_config_path: /etc/openvpn/homelab/homelab.conf
openvpn_static_key_path: /etc/openvpn/homelab/static.key
openvpn_service_name: homelab-openvpn
openvpn_home_routes:
- network: 192.168.1.5
netmask: 255.255.255.255
- network: 192.168.1.6
netmask: 255.255.255.254
- network: 192.168.1.8
netmask: 255.255.255.248
- network: 192.168.1.16
netmask: 255.255.255.240
- network: 192.168.1.32
netmask: 255.255.255.248
- network: 192.168.1.40
netmask: 255.255.255.255
@@ -0,0 +1,6 @@
---
- name: restart openvpn
ansible.builtin.systemd:
name: "{{ openvpn_service_name }}"
state: restarted
enabled: true
@@ -0,0 +1,105 @@
---
- name: Install OpenVPN packages
ansible.builtin.apt:
name:
- openvpn
- iptables
- iproute2
- netcat-openbsd
state: present
update_cache: true
- name: Ensure OpenVPN config directory exists
ansible.builtin.file:
path: "{{ openvpn_config_dir }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Generate static key on server if missing
ansible.builtin.command: "openvpn --genkey secret {{ openvpn_static_key_path }}"
args:
creates: "{{ openvpn_static_key_path }}"
when: openvpn_role == 'server'
no_log: true
- name: Read static key from server
ansible.builtin.slurp:
src: "{{ openvpn_static_key_path }}"
register: openvpn_static_key_raw
when: openvpn_role == 'server'
no_log: true
- name: Copy static key to all peers
ansible.builtin.copy:
dest: "{{ openvpn_static_key_path }}"
content: "{{ hostvars['ru-vps'].openvpn_static_key_raw.content | b64decode }}"
owner: root
group: root
mode: "0600"
no_log: true
notify: restart openvpn
- name: Enable IPv4 forwarding on gateway
ansible.posix.sysctl:
name: net.ipv4.ip_forward
value: "1"
state: present
reload: true
when: openvpn_role == 'gateway'
- name: Allow OpenVPN TCP in UFW on server
ansible.builtin.command: "ufw allow {{ openvpn_listen_port }}/tcp"
register: ufw_allow_openvpn
changed_when: "'Rule added' in ufw_allow_openvpn.stdout or 'Rules updated' in ufw_allow_openvpn.stdout"
failed_when: false
when: openvpn_role == 'server'
- name: Render OpenVPN config
ansible.builtin.template:
src: homelab.conf.j2
dest: "{{ openvpn_config_path }}"
owner: root
group: root
mode: "0600"
notify: restart openvpn
- name: Render OpenVPN gateway up script
ansible.builtin.template:
src: up.sh.j2
dest: "{{ openvpn_config_dir }}/up.sh"
owner: root
group: root
mode: "0700"
notify: restart openvpn
when: openvpn_role == 'gateway'
- name: Render OpenVPN gateway down script
ansible.builtin.template:
src: down.sh.j2
dest: "{{ openvpn_config_dir }}/down.sh"
owner: root
group: root
mode: "0700"
notify: restart openvpn
when: openvpn_role == 'gateway'
- name: Render OpenVPN systemd service
ansible.builtin.template:
src: homelab-openvpn.service.j2
dest: "/etc/systemd/system/{{ openvpn_service_name }}.service"
owner: root
group: root
mode: "0644"
notify: restart openvpn
- name: Reload systemd
ansible.builtin.systemd:
daemon_reload: true
- name: Enable and start OpenVPN
ansible.builtin.systemd:
name: "{{ openvpn_service_name }}"
state: started
enabled: true
@@ -0,0 +1,2 @@
#!/bin/sh
iptables -t nat -D POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE 2>/dev/null || true
@@ -0,0 +1,13 @@
[Unit]
Description=HomeLab OpenVPN {{ openvpn_role }}
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/sbin/openvpn --config {{ openvpn_config_path }}
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,26 @@
dev {{ openvpn_interface }}
ifconfig {{ openvpn_local_ip }} {{ openvpn_peer_ip }}
secret {{ openvpn_static_key_path }}
cipher AES-256-CBC
auth SHA256
persist-key
persist-tun
verb 3
keepalive 10 60
{% if openvpn_role == 'server' %}
proto {{ openvpn_proto_server }}
port {{ openvpn_listen_port }}
{% for route in openvpn_home_routes %}
route {{ route.network }} {{ route.netmask }}
{% endfor %}
{% endif %}
{% if openvpn_role == 'gateway' %}
proto {{ openvpn_proto_client }}
remote 157.22.231.198 {{ openvpn_listen_port }}
resolv-retry infinite
nobind
route-nopull
script-security 2
up /etc/openvpn/homelab/up.sh
down /etc/openvpn/homelab/down.sh
{% endif %}
@@ -0,0 +1,3 @@
#!/bin/sh
iptables -t nat -C POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s {{ openvpn_network_cidr }} -d {{ homelab_lan_cidr }} -o eth0 -j MASQUERADE
+20
View File
@@ -0,0 +1,20 @@
---
pve_lxc_vmid: 132
pve_lxc_node: mini-pc
pve_lxc_hostname: wg-mini
pve_lxc_ip: 192.168.1.23/24
pve_lxc_gateway: 192.168.1.1
pve_lxc_bridge: vmbr0
pve_lxc_storage: local-lvm
pve_lxc_disk: local-lvm:8
pve_lxc_cores: 1
pve_lxc_memory: 256
pve_lxc_swap: 128
pve_lxc_onboot: true
pve_lxc_startup: order=30
pve_lxc_unprivileged: true
pve_lxc_features:
- nesting=1
pve_lxc_nameserver: 1.1.1.1
pve_lxc_pubkey_file: ~/.ssh/id_ed25519_homelab.pub
pve_lxc_cmode: shell
+46
View File
@@ -0,0 +1,46 @@
---
- name: Validate Proxmox API environment
ansible.builtin.assert:
that:
- lookup('env', 'PROXMOX_HOST') | length > 0
- lookup('env', 'PROXMOX_USER') | length > 0
- lookup('env', 'PROXMOX_TOKEN_ID') | length > 0
- lookup('env', 'PROXMOX_TOKEN_SECRET') | length > 0
fail_msg: Load ansible/.env with PROXMOX_HOST, PROXMOX_USER, PROXMOX_TOKEN_ID and PROXMOX_TOKEN_SECRET.
- name: Create or update LXC container
community.proxmox.proxmox:
api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: "{{ lookup('env', 'PROXMOX_VALIDATE_CERTS') | default('false', true) | bool }}"
vmid: "{{ pve_lxc_vmid }}"
node: "{{ pve_lxc_node }}"
hostname: "{{ pve_lxc_hostname }}"
ostemplate: "{{ pve_lxc_ostemplate }}"
disk: "{{ pve_lxc_disk }}"
cores: "{{ pve_lxc_cores }}"
memory: "{{ pve_lxc_memory }}"
swap: "{{ pve_lxc_swap }}"
nameserver: "{{ pve_lxc_nameserver }}"
cmode: "{{ pve_lxc_cmode }}"
netif:
net0: "name=eth0,gw={{ pve_lxc_gateway }},ip={{ pve_lxc_ip }},bridge={{ pve_lxc_bridge }},firewall=1"
pubkey: "{{ lookup('file', pve_lxc_pubkey_file) }}"
features: "{{ pve_lxc_features }}"
unprivileged: "{{ pve_lxc_unprivileged }}"
onboot: "{{ pve_lxc_onboot }}"
startup: "{{ pve_lxc_startup }}"
update: true
state: present
- name: Start LXC container
community.proxmox.proxmox:
api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
validate_certs: "{{ lookup('env', 'PROXMOX_VALIDATE_CERTS') | default('false', true) | bool }}"
vmid: "{{ pve_lxc_vmid }}"
state: started