This commit is contained in:
2026-07-12 08:44:40 +02:00
commit 55991dcb4e
21 changed files with 610 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
ssh_keys/
patchmon_inventory.yml

12
ansible.cfg Normal file
View File

@@ -0,0 +1,12 @@
[defaults]
inventory = patchmon_inventory.yml
localhost_warning = False
interpreter_python = auto_silent
timeout = 30
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ConnectTimeout=30 -o ConnectionAttempts=5
[inventory]
enable_plugins = patchmon.dynamic_inventory.dynamic_inventory

View File

@@ -0,0 +1,11 @@
# Przykładowa konfiguracja logrotate dla logu bezobsługowych przebiegów autopilot.
# Instalacja: sudo cp cron/ansible-autopilot.logrotate /etc/logrotate.d/ansible-autopilot
/var/log/ansible-autopilot.log {
weekly
rotate 8
compress
delaycompress
missingok
notifempty
copytruncate
}

16
cron/crontab.example Normal file
View File

@@ -0,0 +1,16 @@
# Przykładowy wpis crontab dla bezobsługowego zarządzania flotą przez PatchMon + Ansible.
#
# Instalacja:
# crontab -e
# (wklej poniższy wiersz, dostosuj godzinę)
#
# albo jako plik w /etc/cron.d/ (wtedy trzeba dodać pole z użytkownikiem, np. "root"):
# sudo cp crontab.example /etc/cron.d/ansible-autopilot
#
# Cotygodniowo, w niedzielę o 03:30: aktualizacja pakietów + sprzątanie + raport floty.
# Log trafia do /var/log/ansible-autopilot.log (patrz cron/ansible-autopilot.logrotate).
30 3 * * 0 /root/ansible/cron/run-autopilot.sh
# Opcjonalnie: sam raport stanu floty (bez aktualizacji) codziennie o 07:00,
# żeby mieć świeże dane o dysku/reboot-required do porannego przeglądu.
# 0 7 * * * cd /root/ansible && /usr/bin/ansible-playbook -i patchmon_inventory.yml playbooks/playbook_fleet_report.yml --limit "Update-now,localhost" >> /var/log/ansible-autopilot.log 2>&1

32
cron/run-autopilot.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Wrapper do bezobsługowego uruchamiania playbook_autopilot.yml z crona.
# - flock zapobiega nakładającym się uruchomieniom (np. jeśli poprzedni przebieg
# jeszcze trwa, gdy odpala się kolejny).
# - Wynik (stdout+stderr) trafia z timestampem do LOG_FILE.
# - Reboot NIE jest wykonywany automatycznie — playbook_fleet_report.yml tylko
# zgłasza, które hosty go wymagają (patrz reports/).
set -uo pipefail
ANSIBLE_DIR="/root/ansible"
LOCK_FILE="/var/run/ansible-autopilot.lock"
LOG_FILE="/var/log/ansible-autopilot.log"
TARGET_GROUP="${TARGET_GROUP:-Update-now}"
exec 200>"${LOCK_FILE}"
if ! flock -n 200; then
echo "$(date '+%Y-%m-%d %H:%M:%S') [run-autopilot] Poprzedni przebieg wciąż trwa, pomijam." >>"${LOG_FILE}"
exit 0
fi
cd "${ANSIBLE_DIR}"
{
echo "===== $(date '+%Y-%m-%d %H:%M:%S') [run-autopilot] START (target: ${TARGET_GROUP}) ====="
ansible-playbook -i patchmon_inventory.yml \
playbooks/playbook_autopilot.yml \
--limit "${TARGET_GROUP},localhost"
rc=$?
echo "===== $(date '+%Y-%m-%d %H:%M:%S') [run-autopilot] KONIEC (exit code: ${rc}) ====="
} >>"${LOG_FILE}" 2>&1
exit "${rc}"

15
group_vars/all.yml Normal file
View File

@@ -0,0 +1,15 @@
---
# Wspólne ustawienia połączenia dla wszystkich hostów z inwentarza PatchMon.
# playbook_dir sprawia, że ścieżka do klucza działa niezależnie od katalogu,
# z którego wywołujemy ansible-playbook.
ansible_ssh_private_key_file: "{{ playbook_dir }}/../ssh_keys/id_rsa"
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
ansible_become: true
ansible_become_method: sudo
# Klucze publiczne wdrażane przez playbooks/playbook_ssh_keys.yml.
# Uzupełnij o właściwe klucze przed użyciem tego playbooka.
admin_ssh_keys: []
# Próg (%) zajętości dysku, po którym playbooks/playbook_cleanup.yml zgłasza ostrzeżenie.
disk_usage_warn_percent: 85

3
group_vars/mgmt.yml Normal file
View File

@@ -0,0 +1,3 @@
---
# Hosty w grupie PatchMon "mgmt" logują się kontem mgmt i podnoszą uprawnienia przez sudo.
ansible_user: mgmt

3
group_vars/root.yml Normal file
View File

@@ -0,0 +1,3 @@
---
# Hosty w grupie PatchMon "root" logują się bezpośrednio jako root.
ansible_user: root

View File

@@ -0,0 +1,6 @@
---
plugin: patchmon.dynamic_inventory.dynamic_inventory
api_url: http://patchmon.example.local/api/v1/api/hosts
api_key: CHANGE_ME_API_KEY
api_secret: CHANGE_ME_API_SECRET
verify_ssl: false

View File

@@ -0,0 +1,22 @@
---
# Playbook do bezobsługowego uruchamiania z crona (patrz cron/run-autopilot.sh).
# Kolejność: aktualizacja pakietów -> sprzątanie/kondycja -> raport floty.
#
# Świadomie NIE zawiera:
# - playbook_security_baseline.yml (zmiany w sshd/unattended-upgrades — uruchamiać ręcznie)
# - restartu hostów (reboot jest tylko raportowany w fleet_report, decyzję
# o restarcie podejmuje operator ręcznie)
#
# Użycie: ansible-playbook -i patchmon_inventory.yml playbooks/playbook_autopilot.yml --limit "Update-now,localhost"
#
# WAŻNE: ",localhost" w --limit jest wymagane — playbook_fleet_report.yml ma
# drugą fazę na hosts: localhost (renderowanie raportu), a --limit bez "localhost"
# w wzorcu powoduje jej pominięcie ("skipping: no hosts matched").
- name: Aktualizacja pakietów
import_playbook: playbook_upgrade.yml
- name: Sprzątanie i kondycja systemu
import_playbook: playbook_cleanup.yml
- name: Raport stanu floty
import_playbook: playbook_fleet_report.yml

View File

@@ -0,0 +1,46 @@
---
# Rutynowe sprzątanie/kondycja systemu: apt autoremove/clean, czyszczenie journala,
# sprawdzenie zajętości dysku z progiem ostrzegawczym (domyślnie 85%, group_vars/all.yml).
- name: Sprzątanie i kondycja systemu
hosts: all
gather_facts: true
tasks:
- name: Usuń nieużywane pakiety (apt autoremove)
ansible.builtin.apt:
autoremove: yes
purge: yes
when: ansible_os_family in ["Debian", "Ubuntu"]
- name: Wyczyść lokalny cache pobranych pakietów (apt clean)
ansible.builtin.apt:
autoclean: yes
when: ansible_os_family in ["Debian", "Ubuntu"]
- name: Wyczyść dziennik systemowy starszy niż 7 dni (journalctl --vacuum-time)
ansible.builtin.command: journalctl --vacuum-time=7d
register: journal_vacuum
changed_when: "'Vacuuming done' in journal_vacuum.stdout and 'freed' in journal_vacuum.stdout"
when: ansible_service_mgr == "systemd"
- name: Odśwież fakty o zajętości dysku (po sprzątaniu)
ansible.builtin.setup:
filter: ansible_mounts
gather_timeout: 30
- name: Sprawdź zajętość dysku na partycji root
ansible.builtin.set_fact:
root_disk_percent_used: >-
{{ (( (mount.size_total - mount.size_available) / mount.size_total * 100) | round(0, 'ceil')) | int }}
vars:
mount: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | first }}"
when: ansible_mounts | selectattr('mount', 'equalto', '/') | list | length > 0
- name: OSTRZEŻENIE — zajętość dysku powyżej progu
ansible.builtin.debug:
msg: >-
WARN [{{ inventory_hostname }}]: dysk / zajęty w {{ root_disk_percent_used }}%
(próg: {{ disk_usage_warn_percent }}%)
when:
- root_disk_percent_used is defined
- root_disk_percent_used | int >= disk_usage_warn_percent | int

View File

@@ -0,0 +1,56 @@
---
# Czyszczenie nieużywanych obrazów Docker — tylko na hostach, na których Docker
# jest faktycznie zainstalowany (pozostałe hosty są bezpiecznie pomijane).
#
# Domyślnie usuwa wyłącznie obrazy "dangling" (bez tagu) — bezpieczne, nie rusza
# tagowanych obrazów, nawet jeśli akurat nie są używane przez żaden kontener.
#
# Aby usunąć WSZYSTKIE nieużywane obrazy (także tagowane, nieużywane przez żaden
# kontener) — więcej odzyskanego miejsca, ale bardziej agresywne — uruchom z:
# -e "docker_prune_all_images=true"
- name: Czyszczenie nieużywanych obrazów Docker
hosts: all
gather_facts: false
vars:
docker_prune_all_images: false
tasks:
- name: Sprawdź, czy docker jest zainstalowany
ansible.builtin.command: which docker
register: docker_check
changed_when: false
failed_when: false
- name: Pomiń — Docker nie jest zainstalowany na tym hoście
ansible.builtin.debug:
msg: "Docker nie jest zainstalowany na {{ inventory_hostname }} — pomijam."
when: docker_check.rc != 0
- name: Zajętość miejsca przez Dockera przed czyszczeniem
ansible.builtin.command: docker system df
register: docker_df_before
changed_when: false
when: docker_check.rc == 0
- name: Usuń nieużywane obrazy Docker (dangling)
ansible.builtin.command: docker image prune -f
register: prune_result
changed_when: "'Total reclaimed space: 0B' not in prune_result.stdout"
when:
- docker_check.rc == 0
- not docker_prune_all_images
- name: Usuń WSZYSTKIE nieużywane obrazy Docker (także tagowane)
ansible.builtin.command: docker image prune -a -f
register: prune_result_all
changed_when: "'Total reclaimed space: 0B' not in prune_result_all.stdout"
when:
- docker_check.rc == 0
- docker_prune_all_images
- name: Wynik czyszczenia obrazów
ansible.builtin.debug:
msg: >-
{{ (prune_result_all if docker_prune_all_images else prune_result).stdout_lines
| default(['brak danych']) }}
when: docker_check.rc == 0

View File

@@ -0,0 +1,66 @@
---
# Raport stanu floty (tylko odczyt, bez żadnych zmian na hostach):
# OS/wersja, uptime, zajętość dysku, liczba oczekujących aktualizacji
# oraz flaga reboot-required (decyzję o restarcie podejmuje operator ręcznie).
#
# WAŻNE: druga faza (renderowanie raportu) działa na hosts: localhost — jeśli
# używasz --limit, dopisz do niego ",localhost", inaczej ta faza zostanie pominięta,
# np.: --limit "Update-now,localhost"
- name: Zbierz stan floty
hosts: all
gather_facts: true
tasks:
- name: Sprawdź czy wymagany jest restart (reboot-required)
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required_stat
- name: Policz pakiety oczekujące na aktualizację
ansible.builtin.command: apt list --upgradable
register: upgradable_raw
changed_when: false
failed_when: false
when: ansible_os_family in ["Debian", "Ubuntu"]
- name: Sprawdź zajętość dysku na partycji root
ansible.builtin.set_fact:
root_disk_percent_used: >-
{{ (( (mount.size_total - mount.size_available) / mount.size_total * 100) | round(0, 'ceil')) | int }}
vars:
mount: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | first }}"
when: ansible_mounts | selectattr('mount', 'equalto', '/') | list | length > 0
- name: Zbuduj podsumowanie hosta
ansible.builtin.set_fact:
host_report:
os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
uptime_days: "{{ (ansible_uptime_seconds / 86400) | round(1) }}"
disk_root_percent: "{{ root_disk_percent_used | default('n/a') }}"
upgradable_packages: >-
{{ (upgradable_raw.stdout_lines | default([]) | length - 1)
if (upgradable_raw is defined and upgradable_raw.stdout_lines is defined and upgradable_raw.stdout_lines | length > 0)
else 0 }}
reboot_required: "{{ reboot_required_stat.stat.exists }}"
- name: Wygeneruj raport floty
hosts: localhost
connection: local
gather_facts: true
tasks:
- name: Upewnij się, że katalog reports/ istnieje
ansible.builtin.file:
path: "{{ playbook_dir }}/../reports"
state: directory
mode: "0755"
- name: Renderuj raport floty
ansible.builtin.template:
src: "{{ playbook_dir }}/../templates/fleet_report.j2"
dest: "{{ playbook_dir }}/../reports/fleet_report_{{ ansible_date_time.iso8601_basic_short }}.txt"
mode: "0644"
- name: Ścieżka wygenerowanego raportu
ansible.builtin.debug:
msg: "Raport zapisany w reports/fleet_report_{{ ansible_date_time.iso8601_basic_short }}.txt"

View File

@@ -0,0 +1,64 @@
---
# Baza bezpieczeństwa: unattended-upgrades (tylko aktualizacje bezpieczeństwa,
# bez automatycznego restartu — patrz playbook_fleet_report.yml dla flagi reboot-required)
# oraz wyłączenie logowania hasłem po SSH.
- name: Baza bezpieczeństwa (unattended-upgrades + wyłączenie logowania hasłem)
hosts: all
gather_facts: true
tasks:
- name: Zainstaluj unattended-upgrades (Debian/Ubuntu)
ansible.builtin.apt:
name:
- unattended-upgrades
- apt-listchanges
state: present
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family in ["Debian", "Ubuntu"]
- name: Skonfiguruj unattended-upgrades (tylko aktualizacje bezpieczeństwa, bez auto-reboot)
ansible.builtin.template:
src: ../templates/50unattended-upgrades.j2
dest: /etc/apt/apt.conf.d/50unattended-upgrades
owner: root
group: root
mode: "0644"
when: ansible_os_family in ["Debian", "Ubuntu"]
- name: Włącz okresowe aktualizacje APT (20auto-upgrades)
ansible.builtin.copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
owner: root
group: root
mode: "0644"
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
when: ansible_os_family in ["Debian", "Ubuntu"]
- name: Wyłącz logowanie hasłem (PasswordAuthentication no)
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '(?i)^\s*#?\s*PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
backup: yes
validate: '/usr/sbin/sshd -t -f %s'
notify: Restart SSH
- name: Ogranicz logowanie root do klucza (PermitRootLogin prohibit-password)
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '(?i)^\s*#?\s*PermitRootLogin'
line: 'PermitRootLogin prohibit-password'
state: present
backup: yes
validate: '/usr/sbin/sshd -t -f %s'
notify: Restart SSH
handlers:
- name: Restart SSH
ansible.builtin.service:
name: ssh
state: restarted

View File

@@ -0,0 +1,23 @@
---
- name: Konfiguracja SSH (blokada pustych haseł)
hosts: all
gather_facts: true
tasks:
- name: Zablokuj logowanie na puste hasła (PermitEmptyPasswords no)
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
# Szuka linii zaczynającej się od PermitEmptyPasswords (również zakomentowanej)
regexp: '(?i)^\s*#?\s*PermitEmptyPasswords'
line: 'PermitEmptyPasswords no'
state: present
backup: yes
# Sprawdza poprawność pliku przed zapisem, aby nie zepsuć SSH
validate: '/usr/sbin/sshd -t -f %s'
notify: Restart SSH
handlers:
- name: Restart SSH
ansible.builtin.service:
name: ssh
state: restarted

View File

@@ -0,0 +1,32 @@
---
# Zarządzanie kluczami publicznymi SSH konta logowania (root lub mgmt, w zależności
# od grupy PatchMon hosta) na całej flocie jednym uruchomieniem.
#
# Lista kluczy: admin_ssh_keys w group_vars/all.yml (uzupełnij przed użyciem).
#
# Domyślnie tylko DODAJE brakujące klucze (bezpieczne). Aby wymusić, żeby
# authorized_keys zawierał WYŁĄCZNIE klucze z admin_ssh_keys (usuwając inne),
# uruchom z: -e "ssh_keys_exclusive=true" — rób to świadomie, może odciąć dostęp.
- name: Zarządzanie kluczami SSH konta logowania
hosts: all
gather_facts: false
vars:
ssh_keys_exclusive: false
tasks:
- name: Sprawdź, czy admin_ssh_keys nie jest puste
ansible.builtin.assert:
that:
- admin_ssh_keys | length > 0
fail_msg: >-
admin_ssh_keys jest puste — uzupełnij listę kluczy w group_vars/all.yml
przed uruchomieniem tego playbooka.
run_once: true
delegate_to: localhost
- name: Zapewnij obecność kluczy publicznych w authorized_keys
ansible.posix.authorized_key:
user: "{{ ansible_user }}"
key: "{{ admin_ssh_keys | join('\n') }}"
state: present
exclusive: "{{ ssh_keys_exclusive }}"

View File

@@ -0,0 +1,16 @@
---
- name: Aktualizacja pakietów (Debian/Ubuntu)
hosts: all
gather_facts: true
tasks:
- name: Aktualizacja cache pakietów (Debian/Ubuntu)
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
when: ansible_os_family in ["Debian", "Ubuntu"]
- name: Aktualizacja wszystkich pakietów (Debian/Ubuntu)
ansible.builtin.apt:
upgrade: dist
when: ansible_os_family in ["Debian", "Ubuntu"]

139
readme.md Normal file
View File

@@ -0,0 +1,139 @@
# Ansible + PatchMon — zarządzanie flotą serwerów
Inwentarz jest pobierany dynamicznie z PatchMon (`patchmon_inventory.yml`, plugin
`patchmon.dynamic_inventory`). Grupy hostów pochodzą wprost z grup przypisanych
w PatchMon — filtrowanie na potrzeby playbooków (`--limit`) odbywa się przez te grupy,
nie przez ręczne listy hostów.
## Pierwsze uruchomienie (świeży klon repozytorium)
Sekrety nie są trzymane w gicie — trzeba je uzupełnić lokalnie:
```bash
cp patchmon_inventory.yml.example patchmon_inventory.yml
# uzupełnij api_url / api_key / api_secret w patchmon_inventory.yml
mkdir -p ssh_keys && chmod 700 ssh_keys
cp /ścieżka/do/twojego/klucza ssh_keys/id_rsa && chmod 600 ssh_keys/id_rsa
```
## Grupy PatchMon używane w tym projekcie
| Grupa | Znaczenie |
|---|---|
| `root` | Hosty logowane bezpośrednio na konto `root`. |
| `mgmt` | Hosty logowane na konto `mgmt` (z `sudo`). |
| `Update-now` | Wszystkie hosty dopuszczone do automatycznej aktualizacji (`root` `mgmt`). Domyślny cel dla crona/autopilota. |
| `Type-HOST` | Fizyczne hosty Proxmox — traktuj ostrożniej (restart hosta restartuje też jego VM/CT). |
| `Type-VM` / `Type-CT` | Maszyny wirtualne / kontenery LXC. |
| `Site-nni` / `Site-on` | Lokalizacje. |
Konto logowania (`ansible_user`) **nie** jest już podawane ręcznie przez `-e` — jest
ustawiane automatycznie przez `group_vars/root.yml` i `group_vars/mgmt.yml` na
podstawie grupy PatchMon danego hosta. Dzięki temu jedno uruchomienie playbooka na
grupie mieszanej (np. `Update-now`) obsłuży poprawnie oba typy kont.
## Struktura projektu
```
ssh_keys/
id_rsa # klucz prywatny SSH (poza gitem, patrz sekcja "Sekrety")
group_vars/
all.yml # klucz SSH, become, próg dysku, lista kluczy admina
root.yml / mgmt.yml # ansible_user per grupa PatchMon
playbooks/
playbook_upgrade.yml # apt update + dist-upgrade
playbook_cleanup.yml # autoremove/autoclean, czyszczenie journala, alarm dysku
playbook_docker_cleanup.yml # czyszczenie nieużywanych obrazów Docker (tylko hosty z Dockerem)
playbook_fleet_report.yml # raport: OS, uptime, dysk, liczba aktualizacji, reboot-required
playbook_security_baseline.yml # unattended-upgrades + wyłączenie logowania hasłem SSH
playbook_ssh.yml # blokada pustych haseł SSH
playbook_ssh_keys.yml # zarządzanie kluczami publicznymi na koncie logowania
playbook_autopilot.yml # orkiestracja pod cron: upgrade -> cleanup -> fleet_report
templates/
50unattended-upgrades.j2
fleet_report.j2
cron/
run-autopilot.sh # wrapper do crona (flock + log)
crontab.example
ansible-autopilot.logrotate
reports/ # tu trafiają wygenerowane raporty floty
```
## Użycie
```bash
# Aktualizacja pakietów na całej flocie dopuszczonej do auto-update
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_upgrade.yml --limit "Update-now"
# Sprzątanie/kondycja systemu (autoremove, journal, alarm zajętości dysku)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_cleanup.yml --limit "Update-now"
# Czyszczenie nieużywanych obrazów Docker (tylko hosty, na których jest Docker)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_docker_cleanup.yml --limit "Update-now"
# Raport stanu floty -> plik w reports/
# WAŻNE: dopisz ",localhost" do --limit, inaczej faza renderowania raportu
# zostanie pominięta ("skipping: no hosts matched").
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_fleet_report.yml --limit "Update-now,localhost"
# Baza bezpieczeństwa: unattended-upgrades + wyłączenie logowania hasłem SSH
# (dotyka sshd_config — uruchamiaj świadomie, ręcznie, nie z crona)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_security_baseline.yml --limit "Update-now"
# Blokada logowania na puste hasła
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_ssh.yml --limit "Update-now"
# Zarządzanie kluczami SSH konta logowania (uzupełnij admin_ssh_keys w group_vars/all.yml)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_ssh_keys.yml --limit "Update-now"
# Pełny przebieg autopilot ręcznie (to samo co robi cron)
ansible-playbook -i patchmon_inventory.yml playbooks/playbook_autopilot.yml --limit "Update-now,localhost"
```
Można też celować w węższe grupy, np. `--limit "Site-on"`, `--limit "Type-CT"`,
albo pojedynczy host: `--limit "pve"`.
## Reboot — tylko raportowanie, nigdy automatycznie
`playbook_fleet_report.yml` sprawdza `/var/run/reboot-required` na każdym hoście
i wypisuje listę hostów wymagających restartu w raporcie (`reports/fleet_report_*.txt`).
**Żaden playbook w tym projekcie nie restartuje hostów automatycznie** — decyzję
i moment restartu (szczególnie dla `Type-HOST`, czyli fizycznego Proxmoksa) podejmuje
operator ręcznie, po przejrzeniu raportu.
## Bezobsługowe uruchamianie (cron)
`cron/run-autopilot.sh` to wrapper na `playbook_autopilot.yml`:
- blokuje się przez `flock`, żeby nie nakładały się dwa równoległe przebiegi,
- loguje przebieg (start/koniec/kod wyjścia) do `/var/log/ansible-autopilot.log`,
- domyślnie celuje w grupę `Update-now` (nadpisywalne zmienną `TARGET_GROUP`).
Instalacja:
```bash
chmod +x cron/run-autopilot.sh
crontab -e # wklej zawartość cron/crontab.example
sudo cp cron/ansible-autopilot.logrotate /etc/logrotate.d/ansible-autopilot
```
Każdy przebieg `playbook_fleet_report.yml` (w tym z crona) dokłada nowy plik do
`reports/` — warto od czasu do czasu posprzątać stare raporty, np.
`find reports/ -name 'fleet_report_*.txt' -mtime +30 -delete`.
## Sekrety
- **Klucz prywatny SSH** leży w `ssh_keys/id_rsa` (katalog `chmod 700`, plik `chmod 600`).
Katalog `ssh_keys/` jest w `.gitignore` — klucz nigdy nie trafia do repozytorium.
Ścieżka do niego jest ustawiona centralnie w `group_vars/all.yml`
(`ansible_ssh_private_key_file: "{{ playbook_dir }}/../ssh_keys/id_rsa"`), więc
wszystkie playbooki i `cron/run-autopilot.sh` korzystają z niej automatycznie —
nie trzeba nic dodatkowo konfigurować.
- **`api_key`/`api_secret`/`api_url` PatchMon** leżą w `patchmon_inventory.yml`, który
jest w `.gitignore` — nigdy nie trafia do repozytorium. Wersja do repo to
`patchmon_inventory.yml.example` (same placeholdery, bez sekretów). Przy zakładaniu
projektu od zera: `cp patchmon_inventory.yml.example patchmon_inventory.yml` i
uzupełnij `api_url`/`api_key`/`api_secret`.
(Plugin `patchmon.dynamic_inventory` wczytuje ten plik bezpośrednio, bez
templatowania Jinja2/Vault, dlatego to rozdzielenie na plik realny + `.example`,
a nie np. `{{ lookup('env', ...) }}`, jest tu jedynym niezawodnym sposobem.)

0
reports/.gitkeep Normal file
View File

View File

@@ -0,0 +1,13 @@
// Zarządzane przez Ansible (playbook_security_baseline.yml) — nie edytuj ręcznie.
// Instaluje wyłącznie aktualizacje bezpieczeństwa, bez automatycznego restartu:
// decyzję o reboot podejmuje operator na podstawie playbook_fleet_report.yml.
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
"origin=Ubuntu,archive=${distro_codename}-security";
};
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";

33
templates/fleet_report.j2 Normal file
View File

@@ -0,0 +1,33 @@
Raport stanu floty — {{ ansible_date_time.iso8601 }}
{{ '=' * 60 }}
{% set reported_hosts = hostvars | dict2items | selectattr('value.host_report', 'defined') | map(attribute='key') | sort %}
{% set reboot_hosts = [] %}
{% for h in reported_hosts %}
{% if hostvars[h].host_report.reboot_required %}
{% set _ = reboot_hosts.append(h) %}
{% endif %}
{% endfor %}
Hosty ({{ reported_hosts | length }}):
{% for h in reported_hosts %}
{% set r = hostvars[h].host_report %}
- {{ h }}
OS: {{ r.os }}
Uptime: {{ r.uptime_days }} dni
Dysk / zajęty: {{ r.disk_root_percent }}%
Aktualizacje: {{ r.upgradable_packages }} oczekujących
Reboot wymagany: {{ 'TAK' if r.reboot_required else 'nie' }}
{% endfor %}
{{ '=' * 60 }}
{% if reboot_hosts | length > 0 %}
UWAGA — hosty wymagające ręcznego restartu ({{ reboot_hosts | length }}):
{% for h in reboot_hosts %}
- {{ h }}
{% endfor %}
Reboot NIE jest wykonywany automatycznie — zrestartuj te hosty ręcznie,
gdy będzie to bezpieczne (patrz grupy Type-HOST / Type-VM / Type-CT).
{% else %}
Żaden host nie wymaga restartu.
{% endif %}