Fix README, gitignore, add pre-commit hooks

- README: poprawiona nazwa playbooka (mycloud.node.exporter.yml -> mycloud.yml), dodane wszystkie playbooki
- .gitignore: dodano ansible_runs.jsonl, __pycache__/, *.pyc
- git rm --cached: ansible_runs.jsonl, callback_plugins/__pycache__/*.pyc
- .pre-commit-config.yaml: trailing-whitespace, yaml, detect-secrets, blokada vault.pass i niezaszyfrowanych host_vars
- .secrets.baseline: baseline dla detect-secrets
- Auto-fix: trailing whitespace i brakujace EOF w plikach repo
This commit is contained in:
blasebast 2026-04-10 23:37:20 +02:00
parent 1d03b2b286
commit 67fbacdef6
27 changed files with 303 additions and 80 deletions

6
.gitignore vendored
View File

@ -21,6 +21,12 @@
# Logs
*.log
logs/
ansible_runs.jsonl
# Python
__pycache__/
*.pyc
*.pyo
# OS
.DS_Store

42
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,42 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: [--unsafe] # ansible vault files have !vault tag
- id: check-added-large-files
args: [--maxkb=500]
- id: detect-private-key
- id: check-merge-conflict
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: [--baseline, .secrets.baseline]
exclude: inventory/host_vars/.* # juz szyfrowane vault'em
- repo: local
hooks:
- id: no-vault-pass
name: vault.pass nie moze byc commitowany
entry: bash
args: [-c, 'git diff --cached --name-only | grep -q "vault.pass" && echo "ERROR: vault.pass jest w staged files!" && exit 1 || true']
language: system
pass_filenames: false
- id: no-unencrypted-hostvars
name: host_vars musza byc zaszyfrowane
entry: bash
args:
- -c
- |
for f in $(git diff --cached --name-only | grep "inventory/host_vars/"); do
if ! head -1 "$f" 2>/dev/null | grep -q '^\$ANSIBLE_VAULT'; then
echo "ERROR: $f nie jest zaszyfrowany!"; exit 1
fi
done
language: system
pass_filenames: false

133
.secrets.baseline Normal file
View File

@ -0,0 +1,133 @@
{
"version": "1.5.0",
"plugins_used": [
{
"name": "ArtifactoryDetector"
},
{
"name": "AWSKeyDetector"
},
{
"name": "AzureStorageKeyDetector"
},
{
"name": "Base64HighEntropyString",
"limit": 4.5
},
{
"name": "BasicAuthDetector"
},
{
"name": "CloudantDetector"
},
{
"name": "DiscordBotTokenDetector"
},
{
"name": "GitHubTokenDetector"
},
{
"name": "GitLabTokenDetector"
},
{
"name": "HexHighEntropyString",
"limit": 3.0
},
{
"name": "IbmCloudIamDetector"
},
{
"name": "IbmCosHmacDetector"
},
{
"name": "IPPublicDetector"
},
{
"name": "JwtTokenDetector"
},
{
"name": "KeywordDetector",
"keyword_exclude": ""
},
{
"name": "MailchimpDetector"
},
{
"name": "NpmDetector"
},
{
"name": "OpenAIDetector"
},
{
"name": "PrivateKeyDetector"
},
{
"name": "PypiTokenDetector"
},
{
"name": "SendGridDetector"
},
{
"name": "SlackDetector"
},
{
"name": "SoftlayerDetector"
},
{
"name": "SquareOAuthDetector"
},
{
"name": "StripeDetector"
},
{
"name": "TelegramBotTokenDetector"
},
{
"name": "TwilioKeyDetector"
}
],
"filters_used": [
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
},
{
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
},
{
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
},
{
"path": "detect_secrets.filters.heuristic.is_lock_file"
},
{
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
},
{
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
},
{
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
},
{
"path": "detect_secrets.filters.heuristic.is_sequential_string"
},
{
"path": "detect_secrets.filters.heuristic.is_swagger_file"
},
{
"path": "detect_secrets.filters.heuristic.is_templated_secret"
},
{
"path": "detect_secrets.filters.regex.should_exclude_file",
"pattern": [
"inventory/host_vars/.*"
]
}
],
"results": {},
"generated_at": "2026-04-10T21:35:59Z"
}

130
README.md
View File

@ -1,57 +1,109 @@
# Ansible Configuration Management
This repository contains Ansible playbooks and roles for managing my personal infrastructure.
Repozytorium zarządza prywatną infrastrukturą: NAS (MyCloud, Asustor), RPI4, komputer (acemagic), router MikroTik.
## Main Playbooks
Parametry `-i inventory/hosts.yml` i `--vault-password-file=vault.pass` sa skonfigurowane w `ansible.cfg` i mozna je pominac.
### Common Configuration
Runs the main configuration playbook to manage services, packages, and system settings:
---
## Inwentarz
| Host | IP | Rola |
|---|---|---|
| acemagic | 192.168.1.132 | komputer glowny (backupy, crony, autofs) |
| mycloud | 192.168.1.219 | NAS WD MyCloud |
| asustor-lan1 | 192.168.1.10:8883 | NAS Asustor |
| rpi4 | 192.168.1.151 | Raspberry Pi 4 |
| mikrotik | 192.168.1.1 | router |
Zmienne hostow sa szyfrowane Ansible Vault w `inventory/host_vars/`.
---
## Playbooki
### Wspolna konfiguracja (wszystkie serwery oprocz mikrotik)
```bash
ansible-playbook -i inventory/hosts.yml playbooks/common.yml --vault-password-file=vault.pass
ansible-playbook playbooks/common.yml
```
### Node Exporter for MyCloud
Starts the Node Exporter service on the MyCloud server:
### MyCloud - SSH keys + node_exporter
```bash
ansible-playbook -i inventory/hosts.yml -l mycloud playbooks/mycloud.node.exporter.yml --vault-password-file=vault.pass
ansible-playbook -l mycloud playbooks/mycloud.yml
```
### Update Hosts File
Updates the hosts file on all machines:
### Asustor - SSH keys + node_exporter
```bash
ansible-playbook -i inventory/hosts.yml playbooks/common.yml --tags "update_hosts"
ansible-playbook -l asustor-lan1 playbooks/asustor.yml
```
### Manage Cron Jobs (Debian only)
Manages cron jobs on Debian-based systems:
### Node Exporter przez cloudalchemy (Linux)
```bash
ansible-playbook -i inventory/hosts.yml playbooks/crons-only.yml --vault-password-file=vault.pass
ansible-playbook playbooks/cloudalchemy.node.exporter.yml
```
## Encrypted Variables
Variable files in `inventory/host_vars/` are encrypted using Ansible Vault for security. The vault password is stored in `vault.pass` which is included in `.gitignore` to prevent sensitive data leaks.
### Managing Encrypted Files
- To edit encrypted files:
```bash
ansible-vault edit inventory/host_vars/example.yml --vault-password-file=vault.pass
```
- To decrypt all variables (before git operations):
```bash
bash decrypt_vars.sh
```
- To re-encrypt after making changes:
```bash
bash encrypt_vars.sh
```
## Example Host Variables
```yaml
ansible_ssh_port: 22
ansible_ssh_user: username
# Other host-specific variables
### Crony na acemagic
```bash
ansible-playbook playbooks/crons-only.yml
```
## Dependencies
- [cloudalchemy.node-exporter](https://galaxy.ansible.com/cloudalchemy/node-exporter)
### Backupy na acemagic
```bash
ansible-playbook playbooks/acemagic.backups.yml
```
### Autofs (NAS mounts na acemagic)
```bash
ansible-playbook playbooks/autofs.yml
```
### Backup MikroTik
```bash
ansible-playbook playbooks/mikrotik.backup.yml
```
### PostgreSQL replication (acemagic -> asustor)
```bash
ansible-playbook playbooks/setup_postgres_replication.yml
```
### rsync z MyCloud
```bash
ansible-playbook playbooks/rsync-mycloud.yml
```
### pureFTPd + doorbell
```bash
ansible-playbook playbooks/pureftpd_doorbell.yml
```
---
## Szyfrowanie zmiennych
Pliki `inventory/host_vars/*.yml` sa szyfrowane Ansible Vault (AES256).
Haslo vault trzymane jest w `vault.pass` (wykluczone z git przez `.gitignore`).
```bash
# Edycja zaszyfrowanego pliku
ansible-vault edit inventory/host_vars/mycloud.yml
# Odszyfrowanie wszystkich (przed git operations)
bash decrypt_vars.sh
# Ponowne zaszyfrowanie po zmianach
bash encrypt_vars.sh
```
---
## Logging
Callback plugin `callback_plugins/json_logger.py` zapisuje wyniki uruchomien do `ansible_runs.jsonl` (wykluczone z git).
---
## Zaleznosci
- `geerlingguy.nfs` - zainstalowana lokalnie w `collections/`
- `cloudalchemy.node-exporter` - instalacja: `ansible-galaxy install cloudalchemy.node-exporter`

View File

@ -494,4 +494,3 @@ ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=
# Set how many context lines to show in diff
# context = 3

View File

@ -1,2 +0,0 @@
{"ts": "2026-04-10T21:09:25", "playbook": "test_logger.yml", "user": "seba", "duration": 1.8, "status": "success", "hosts": {"localhost": {"ok": 2, "changed": 0, "failed": 0, "unreachable": 0, "skipped": 0}}, "tasks": ["Gathering Facts", "test task"]}
{"ts": "2026-04-10T21:21:32", "playbook": "crons-only.yml", "user": "seba", "duration": 4.6, "status": "success", "hosts": {"acemagic": {"ok": 5, "changed": 0, "failed": 0, "unreachable": 0, "skipped": 0}}, "tasks": ["Gathering Facts", "../roles/crons.acemagic : Restart Frigate Docker container", "../roles/crons.acemagic : Backup Bitwarden database", "../roles/crons.acemagic : Cron: Keep-alive hack for stubborn HDDs (Seagate 5T & 6T)", "../roles/crons.acemagic : Configure hdparm to prevent spindown for supported drives (SDE & SDB)"]}

View File

@ -10,4 +10,4 @@
#
{% for export in nfs_exports %}
{{ export }}
{% endfor %}
{% endfor %}

View File

@ -3,4 +3,4 @@ source include.sh
for file in $(ls inventory/host_vars/*.yml); do
common::log "decrypting: ${file}"
ansible-vault decrypt ${file} --vault-password-file=vault.pass
done
done

View File

@ -25,4 +25,4 @@ function common::log_fail {
#ANSIBLE_INVENTORY="${PWD}/inventory/hosts.yml"
#[[ -e "${ANSIBLE_INVENTORY}" ]] || (common::log_fail "${ANSIBLE_INVENTORY} missing!"; exit 1)
#[[ -e "${ANSIBLE_INVENTORY}" ]] || (common::log_fail "${ANSIBLE_INVENTORY} missing!"; exit 1)

View File

@ -6,5 +6,3 @@
- hosts: acemagic
roles:
- ../roles/crons.acemagic

View File

@ -2,4 +2,3 @@
- hosts: acemagic
roles:
- ../roles/crons.acemagic

View File

@ -57,4 +57,3 @@
- service_properties_to_update.changed
tags:
- always

View File

@ -3,4 +3,3 @@
become: true
roles:
- ../roles/pureftpd_doorbell

View File

@ -2,4 +2,3 @@
- hosts: mycloud
roles:
- ../roles/rsync.mycloud

View File

@ -1,6 +1,6 @@
---
- name: Setup PostgreSQL Logical Replication (Multi-DB)
hosts:
hosts:
- acemagic
- asustor-lan1
roles:

View File

@ -42,7 +42,7 @@
src: auto.mycloud.j2
dest: /etc/auto.mycloud
notify: restart autofs
- name: Ensure autofs is started and enabled
service:
name: autofs

View File

@ -6,4 +6,4 @@ db_user: "homeassistant"
backup_seagata16t_source: "/media/seagata16t/"
backup_seagata16t_destination: "/media/asustor/asustor_volume1/seagata16t-backup/"
backup_seagata16t_script_path: "/usr/local/bin/rsync_backup_seagata.sh"
backup_seagata16t_script_path: "/usr/local/bin/rsync_backup_seagata.sh"

View File

@ -3,7 +3,7 @@
- name: configure hostname
hostname:
name: "{{ common_hostname }}"
when: inventory_hostname is not search('asustor')
when: inventory_hostname is not search('asustor')
- name: Install apt packages
apt:
@ -12,14 +12,14 @@
state: present
update_cache: yes
# Wykonaj wszędzie, gdzie jest APT (nie na Asustorze)
when: inventory_hostname is not search('asustor')
when: inventory_hostname is not search('asustor')
- name: Install PostgreSQL dependencies for Ansible
apt:
name: python3-psycopg2
state: present
become: yes
when: inventory_hostname is not search('asustor')
become: yes
when: inventory_hostname is not search('asustor')
- name: Stop and disable systemd units
systemd:
@ -28,7 +28,7 @@
state: "stopped"
enabled: "no"
loop: "{{ common_systemd_disabled_services | flatten(levels=1) }}"
when: inventory_hostname is not search('asustor')
when: inventory_hostname is not search('asustor')
# --- HOSTS FILE (PRO FIX) ---
# Działa wszędzie (Asustor też ma /etc/hosts, więc tu nie blokujemy)
@ -39,15 +39,15 @@
marker: "# {mark} ANSIBLE MANAGED LAN HOSTS"
state: absent
tags: update_hosts
when: "'mikrotik' not in inventory_hostname"
when: "'mikrotik' not in inventory_hostname"
- name: PRO FIX 2/3 - Cleanup loose LAN lines
lineinfile:
path: /etc/hosts
regexp: '192\.168\.'
regexp: '192\.168\.'
state: absent
tags: update_hosts
when: "'mikrotik' not in inventory_hostname"
when: "'mikrotik' not in inventory_hostname"
- name: PRO FIX 3/3 - Insert block (KEEP TEMPLATE ORDER)
blockinfile:
@ -61,7 +61,7 @@
{% endif %}
{% endfor %}
tags: update_hosts
when: "'mikrotik' not in inventory_hostname"
when: "'mikrotik' not in inventory_hostname"
# --- SSH KEYS MANAGEMENT ---
@ -108,8 +108,8 @@
- name: Dodaj trase dla multicast (SSDP/mDNS)
ansible.builtin.command: ip route add 224.0.0.0/4 dev {{ ansible_default_ipv4.interface }}
register: route_result
failed_when:
- route_result.rc != 0
failed_when:
- route_result.rc != 0
- "'File exists' not in route_result.stderr"
changed_when: route_result.rc == 0
become: yes
@ -163,5 +163,5 @@
systemd:
name: vsftpd
state: started
enabled: yes
enabled: yes
when: inventory_hostname == 'acemagic'

View File

@ -11,4 +11,4 @@ User=root
ExecStart=/usr/bin/dummy.service.sh
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target

View File

@ -1,2 +1,2 @@
#!/bin/bash
echo "{{ common_hostname }}_dummy.service_$(date +'%H%M%S')" >> /tmp/dummy.service.ran.$(date +'%d%m%Y')
echo "{{ common_hostname }}_dummy.service_$(date +'%H%M%S')" >> /tmp/dummy.service.ran.$(date +'%d%m%Y')

View File

@ -46,8 +46,7 @@
192.168.1.190 mac-wday MacBook-Pro
192.168.1.191 iseb13pro my-iphone iPhone
192.168.1.193 SonosZP
192.168.1.196 dell-latitude dell
192.168.1.196 dell-latitude dell
192.168.1.212 NOMI-IPC-A22E-B
192.168.1.219 mycloud MyCloudEX2Ultra
192.168.1.224 yeelink-light-strip2_miapd462

View File

@ -2,4 +2,4 @@
- name: Ensure containers are present
docker_compose:
project_src: "{{ project_src }}"
state: present
state: present

View File

@ -1,6 +1,6 @@
---
acemagic_ip: "192.168.1.132"
rep_user: "homeassistant"
acemagic_ip: "192.168.1.132"
rep_user: "homeassistant"
rep_pass: "slicer_grazing3_landowner_wrench"
replication_databases:

View File

@ -32,6 +32,6 @@
PUBLICATION {{ item.item.pub }};
# Iterujemy po wynikach rejestru sub_check
with_items: "{{ sub_check.results | default([]) }}"
when:
when:
- "'replica' in group_names"
- item.rowcount is defined and item.rowcount == 0

View File

@ -1,7 +1,7 @@
#!/bin/bash
#
# BASH Shell script to flash a Sonoff DIY module Over The Air.
#
#
FIRMWARE_URL_BASE="http://sonoff-ota.aelius.com/"
DEFAULT_FILENAME="tasmota-latest-lite.bin"
@ -107,7 +107,7 @@ discover_module() {
echo "IPv4 Address: ${IPADDRESS}"
echo
}
display_info() {
echo "Getting Module Info..."
sonoff_http_request "${IPADDRESS}" info
@ -242,7 +242,7 @@ parse_options() {
display_help
fi
# If the filename doesn't start http: then prepend the base URL
# If the filename doesn't start http: then prepend the base URL
if [[ ! ${FIRMWARE_URL} =~ ^https?: ]]; then
FIRMWARE_URL="${FIRMWARE_URL_BASE}${FIRMWARE_URL}"
fi
@ -266,7 +266,7 @@ main() {
ota_unlock
ota_flash
echo "Please wait for your device to finish flashing."
}

View File

@ -169,7 +169,7 @@ def main():
for filepath, content in STRUCTURE.items():
full_path = BASE_DIR / filepath
full_path.parent.mkdir(parents=True, exist_ok=True)
if full_path.exists():
print(f" [SKIP] {filepath} (already exists)")
else:
@ -183,7 +183,7 @@ def main():
if "asustor:" not in content or "ansible_port: 8883" not in content:
print(f"\n [INFO] Add this to {hosts_file} under 'all:children:':")
print(HOSTS_ADDITION)
print("\n" + "="*60)
print("DONE! Next steps:")
print("="*60)