Add Ansible playbooks, inventory, and scripts for initial setup
Includes: - `bootstrap-debian13.yml` for system setup and user configuration - `firewall-iptables.yml` for IPv4 firewall management - `run-playbook.sh` and `check.sh` scripts for playbook execution and validation - `inventory.ini` for host definitions - Template for iptables rules at `templates/iptables/rules.v4.j2` - `README.md` with usage instructions
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
## General
|
||||
#### Syntax Check
|
||||
ansible-playbook -i inventory.ini bootstrap-debian13.yml --syntax-check -vvvv
|
||||
|
||||
# Step 1
|
||||
ansible-playbook -i inventory.ini bootstrap-debian13.yml
|
||||
|
||||
82
bootstrap-debian13.yml
Normal file
82
bootstrap-debian13.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
- name: Finlog Bootstrap
|
||||
hosts: finlog_dev
|
||||
become: true
|
||||
gather_facts: false
|
||||
collections:
|
||||
- ansible.posix
|
||||
|
||||
vars:
|
||||
dev_user: "bummsa"
|
||||
dev_user_pubkey: "{{ lookup('file', '~/.ssh/finlog-bummsa.pub') }}"
|
||||
|
||||
base_packages:
|
||||
- sudo
|
||||
- vim
|
||||
- htop
|
||||
- curl
|
||||
- wget
|
||||
- git
|
||||
- unzip
|
||||
- ca-certificates
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- openssh-server
|
||||
- iptables
|
||||
- iptables-persistent
|
||||
- netfilter-persistent
|
||||
|
||||
tasks:
|
||||
- name: Update apt cache
|
||||
become: true
|
||||
ansible.builtin.apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: Install base packages
|
||||
ansible.builtin.apt:
|
||||
name: "{{ base_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Create dev user
|
||||
ansible.builtin.user:
|
||||
name: "{{ dev_user }}"
|
||||
shell: /bin/bash
|
||||
create_home: yes
|
||||
groups: sudo
|
||||
append: yes
|
||||
|
||||
- name: Ensure /etc/sudoers.d directory exists
|
||||
ansible.builtin.file:
|
||||
path: /etc/sudoers.d
|
||||
state: directory
|
||||
mode: '0750'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: Add passwordless sudo for dev user
|
||||
ansible.builtin.copy:
|
||||
dest: "/etc/sudoers.d/{{ dev_user }}"
|
||||
content: "{{ dev_user }} ALL=(ALL) NOPASSWD:ALL\n"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0440'
|
||||
validate: '/usr/sbin/visudo -cf %s'
|
||||
|
||||
- name: Add SSH key for dev user
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ dev_user }}"
|
||||
key: "{{ dev_user_pubkey }}"
|
||||
state: present
|
||||
path: "/home/{{ dev_user }}/.ssh/authorized_keys"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Show what would be done for SSH key in check mode
|
||||
ansible.builtin.debug:
|
||||
msg: "Would add SSH key to /home/{{ dev_user }}/.ssh/authorized_keys"
|
||||
when: ansible_check_mode
|
||||
|
||||
- name: Upgrade system packages
|
||||
ansible.builtin.apt:
|
||||
upgrade: dist
|
||||
autoremove: yes
|
||||
autoclean: yes
|
||||
36
firewall-iptables.yml
Normal file
36
firewall-iptables.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
- name: Configure IPv4 firewall via iptables
|
||||
hosts: finlog_dev
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
firewall_tcp_ports: [ 22, 80, 443 ] # extend as needed
|
||||
firewall_udp_ports: [ ] # e.g. [53]
|
||||
|
||||
tasks:
|
||||
- name: Render IPv4 firewall rules from template
|
||||
ansible.builtin.template:
|
||||
src: iptables/rules.v4.j2
|
||||
dest: /etc/iptables/rules.v4
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Restore rules now
|
||||
ansible.builtin.shell: iptables-restore < /etc/iptables/rules.v4
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Save rules for persistence
|
||||
ansible.builtin.command: netfilter-persistent save
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Show filter table (iptables -S)
|
||||
ansible.builtin.command: iptables -S
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
0
group_vars/all.yml
Normal file
0
group_vars/all.yml
Normal file
2
inventory.ini
Normal file
2
inventory.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[finlog_dev]
|
||||
localdev.host.getfinlog.app ansible_become=true ansible_ssh_private_key_file=~/.ssh/finlog-bummsa
|
||||
137
scripts/check.sh
Executable file
137
scripts/check.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/bin/bash
|
||||
# check.sh
|
||||
# Usage:
|
||||
# ./check.sh <playbook.yml> [inventory.ini] [limit]
|
||||
# Examples:
|
||||
# ./check.sh bootstrap-debian13.yml
|
||||
# ./check.sh bootstrap-debian13.yml inventory.ini finlog_dev
|
||||
# ./check.sh bootstrap-debian13.yml inventory.ini localdev.host.getfinlog.app
|
||||
#
|
||||
# Behavior:
|
||||
# - Attempt 1: Dry-run with key auth as user ${SSH_KEY_USER:-bummsa}
|
||||
# - If SSH/unreachable: Attempt 2 with --ask-pass as root and -e ansible_become=false
|
||||
# - If still unreachable: exit 0 (treated as “validated”); otherwise propagate error
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PLAYBOOK="${1:-}"
|
||||
INVENTORY="${2:-inventory.ini}"
|
||||
LIMIT_ARG="${3:-}"
|
||||
|
||||
# default SSH key user (can override: SSH_KEY_USER=admin ./check.sh ...)
|
||||
SSH_KEY_USER="${SSH_KEY_USER:-bummsa}"
|
||||
|
||||
if [ -z "$PLAYBOOK" ]; then
|
||||
echo "Usage: $0 <playbook.yml> [inventory.ini] [limit]"
|
||||
exit 1
|
||||
fi
|
||||
[ -f "$PLAYBOOK" ] || { echo "❌ Playbook not found: $PLAYBOOK"; exit 1; }
|
||||
[ -f "$INVENTORY" ] || { echo "❌ Inventory not found: $INVENTORY"; exit 1; }
|
||||
|
||||
echo "🔍 Playbook: $PLAYBOOK"
|
||||
echo "📂 Inventory: $INVENTORY"
|
||||
[ -n "$LIMIT_ARG" ] && echo "🎯 Limit: $LIMIT_ARG"
|
||||
echo "👤 Dry-run key user: $SSH_KEY_USER"
|
||||
echo
|
||||
|
||||
# --- helpers ---
|
||||
is_unreachable_output() {
|
||||
grep -Eq 'UNREACHABLE!|Failed to connect to the host via ssh|Permission denied \(publickey,password\)|Permission denied, please try again|No route to host|Could not resolve hostname|Name or service not known|Host key verification failed' "$1"
|
||||
}
|
||||
|
||||
PLAY_HAS_BECOME=false
|
||||
if grep -Eq '^\s*become:\s*true\b' "$PLAYBOOK"; then
|
||||
PLAY_HAS_BECOME=true
|
||||
fi
|
||||
|
||||
ANSIBLE_LIMIT_OPTS=()
|
||||
[ -n "$LIMIT_ARG" ] && ANSIBLE_LIMIT_OPTS=(--limit "$LIMIT_ARG")
|
||||
|
||||
# 1) YAML lint (soft)
|
||||
if command -v yamllint >/dev/null 2>&1; then
|
||||
echo "=== YAML Lint ==="
|
||||
yamllint "$PLAYBOOK"
|
||||
echo
|
||||
else
|
||||
echo "⚠️ yamllint not installed, skipping…"
|
||||
echo
|
||||
fi
|
||||
|
||||
# 2) ansible-lint (soft)
|
||||
if command -v ansible-lint >/dev/null 2>&1; then
|
||||
echo "=== Ansible Lint ==="
|
||||
ansible-lint "$PLAYBOOK"
|
||||
echo
|
||||
else
|
||||
echo "⚠️ ansible-lint not installed, skipping…"
|
||||
echo
|
||||
fi
|
||||
|
||||
# 3) Inventory check (hard)
|
||||
echo "=== Inventory Validation ==="
|
||||
ansible-inventory -i "$INVENTORY" --list >/dev/null
|
||||
echo "✅ Inventory OK"
|
||||
echo
|
||||
|
||||
# 4) Syntax check (hard)
|
||||
echo "=== Ansible Syntax Check ==="
|
||||
ansible-playbook -i "$INVENTORY" "${ANSIBLE_LIMIT_OPTS[@]}" -u "$SSH_KEY_USER" "$PLAYBOOK" --syntax-check
|
||||
echo "✅ Syntax OK"
|
||||
echo
|
||||
|
||||
# 5) Targets info (soft)
|
||||
echo "=== Target Hosts (per play) ==="
|
||||
ansible-playbook -i "$INVENTORY" "${ANSIBLE_LIMIT_OPTS[@]}" -u "$SSH_KEY_USER" "$PLAYBOOK" --list-hosts || true
|
||||
echo
|
||||
|
||||
# 6) Dry run – Attempt 1 (Key as $SSH_KEY_USER)
|
||||
echo "=== Dry Run (Check Mode) — Attempt 1 (Key as $SSH_KEY_USER) ==="
|
||||
TMP1="$(mktemp)"
|
||||
set +e
|
||||
ansible-playbook -i "$INVENTORY" "${ANSIBLE_LIMIT_OPTS[@]}" -u "$SSH_KEY_USER" "$PLAYBOOK" --check --diff 2>&1 | tee "$TMP1"
|
||||
RC1=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ $RC1 -eq 0 ]; then
|
||||
echo
|
||||
echo "✅ Dry Run erfolgreich (Key as $SSH_KEY_USER)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 7) Fallback — Attempt 2 (Password as root, no become)
|
||||
if is_unreachable_output "$TMP1"; then
|
||||
echo
|
||||
echo "⚠️ Host unreachable/SSH failed im ersten Versuch."
|
||||
if [ -t 0 ]; then
|
||||
echo "🔁 Starte zweiten Versuch mit Passwort-Login (als root, ohne become)…"
|
||||
TMP2="$(mktemp)"
|
||||
set +e
|
||||
ansible-playbook -i "$INVENTORY" "${ANSIBLE_LIMIT_OPTS[@]}" "$PLAYBOOK" --check --diff --ask-pass -u root -e ansible_become=false 2>&1 | tee "$TMP2"
|
||||
RC2=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [ $RC2 -eq 0 ]; then
|
||||
echo
|
||||
echo "✅ Dry Run erfolgreich (Password-Fallback als root)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if is_unreachable_output "$TMP2"; then
|
||||
echo
|
||||
echo "⚠️ Auch mit Passwort-Fallback unreachable. Ignoriere für Dry-Run (Exit 0)."
|
||||
echo " Tipp: Key verteilen (ssh-copy-id) oder Zugang prüfen."
|
||||
exit 0
|
||||
else
|
||||
echo
|
||||
echo "❌ Dry Run fehlgeschlagen (kein reiner SSH/Unreachable-Fehler)."
|
||||
exit $RC2
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ Keine TTY verfügbar → kann kein Passwort abfragen. Ignoriere Unreachable für Dry-Run (Exit 0)."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo
|
||||
echo "❌ Dry Run fehlgeschlagen (kein SSH/Unreachable-Thema)."
|
||||
exit $RC1
|
||||
fi
|
||||
136
scripts/run-playbook.sh
Executable file
136
scripts/run-playbook.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/bin/bash
|
||||
# run-playbook.sh
|
||||
# Usage:
|
||||
# ./run-playbook.sh <playbook.yml> [inventory.ini] [limit]
|
||||
# Behavior:
|
||||
# 1) Attempt 1: key auth as user "bummsa" (override with SSH_KEY_USER)
|
||||
# 2) Attempt 2: --ask-pass as root, -e ansible_become=false
|
||||
# 3) Attempt 3 (optional): --ask-pass as SSH_KEY_USER
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PLAYBOOK="${1:-}"
|
||||
INVENTORY="${2:-inventory.ini}"
|
||||
LIMIT_ARG="${3:-}"
|
||||
|
||||
SSH_KEY_USER="${SSH_KEY_USER:-bummsa}"
|
||||
FINAL_FALLBACK_AS_USER="${FINAL_FALLBACK_AS_USER:-yes}"
|
||||
|
||||
if [[ -z "$PLAYBOOK" ]]; then
|
||||
echo "Usage: $0 <playbook.yml> [inventory.ini] [limit]"
|
||||
exit 1
|
||||
fi
|
||||
[[ -f "$PLAYBOOK" ]] || { echo "❌ Playbook not found: $PLAYBOOK"; exit 1; }
|
||||
[[ -f "$INVENTORY" ]] || { echo "❌ Inventory not found: $INVENTORY"; exit 1; }
|
||||
|
||||
echo "🚀 Running playbook"
|
||||
echo "📄 Playbook: $PLAYBOOK"
|
||||
echo "📂 Inventory: $INVENTORY"
|
||||
[[ -n "$LIMIT_ARG" ]] && echo "🎯 Limit: $LIMIT_ARG"
|
||||
echo "👤 Key-auth user (Attempt 1): $SSH_KEY_USER"
|
||||
echo
|
||||
|
||||
# Helpers
|
||||
is_unreachable_output() {
|
||||
grep -Eq \
|
||||
'UNREACHABLE!|Failed to connect to the host via ssh|Permission denied \(publickey,password\)|Permission denied, please try again|No route to host|Could not resolve hostname|Name or service not known|Host key verification failed' \
|
||||
"$1"
|
||||
}
|
||||
|
||||
ANSIBLE_LIMIT_OPTS=()
|
||||
[[ -n "$LIMIT_ARG" ]] && ANSIBLE_LIMIT_OPTS=(--limit "$LIMIT_ARG")
|
||||
|
||||
echo "=== Inventory Validation ==="
|
||||
ansible-inventory -i "$INVENTORY" --list >/dev/null
|
||||
echo "✅ Inventory OK"
|
||||
echo
|
||||
|
||||
# ---- Attempt 1: key auth as SSH_KEY_USER ----
|
||||
echo "=== Attempt 1: Key auth as ${SSH_KEY_USER} ==="
|
||||
TMP1="$(mktemp)"
|
||||
set +e
|
||||
ansible-playbook -i "$INVENTORY" "${ANSIBLE_LIMIT_OPTS[@]-}" -u "$SSH_KEY_USER" -b "$PLAYBOOK" 2>&1 | tee "$TMP1"
|
||||
RC1=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [[ $RC1 -eq 0 ]]; then
|
||||
echo
|
||||
echo "✅ Run successful (key auth as $SSH_KEY_USER)"
|
||||
rm -f "$TMP1"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! is_unreachable_output "$TMP1"; then
|
||||
echo
|
||||
echo "❌ Run failed on non-SSH error (see output above)."
|
||||
echo " Log: $TMP1"
|
||||
exit $RC1
|
||||
fi
|
||||
|
||||
# Need interactive terminal for password prompts
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo
|
||||
echo "❌ SSH unreachable and no TTY available for password prompt."
|
||||
echo " Fix key auth or run interactively. Log: $TMP1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---- Attempt 2: password as root (no become) ----
|
||||
echo
|
||||
echo "⚠️ Key auth failed. Falling back to password as root (no become)…"
|
||||
echo "=== Attempt 2: Password as root (no become) ==="
|
||||
TMP2="$(mktemp)"
|
||||
set +e
|
||||
ansible-playbook -i "$INVENTORY" "${ANSIBLE_LIMIT_OPTS[@]-}" "$PLAYBOOK" --ask-pass -u root -e ansible_become=false 2>&1 | tee "$TMP2"
|
||||
RC2=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [[ $RC2 -eq 0 ]]; then
|
||||
echo
|
||||
echo "✅ Run successful (password auth as root)"
|
||||
rm -f "$TMP1" "$TMP2"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! is_unreachable_output "$TMP2"; then
|
||||
echo
|
||||
echo "❌ Run failed on non-SSH error during password-as-root attempt."
|
||||
echo " Logs:"
|
||||
echo " - Attempt 1: $TMP1 (rc=$RC1)"
|
||||
echo " - Attempt 2: $TMP2 (rc=$RC2)"
|
||||
exit $RC2
|
||||
fi
|
||||
|
||||
# ---- Attempt 3: optional password as SSH_KEY_USER ----
|
||||
if [[ "$FINAL_FALLBACK_AS_USER" == "yes" ]]; then
|
||||
echo
|
||||
echo "⚠️ Still unreachable. Trying password as ${SSH_KEY_USER} (final fallback)…"
|
||||
echo "=== Attempt 3: Password as ${SSH_KEY_USER} ==="
|
||||
TMP3="$(mktemp)"
|
||||
set +e
|
||||
ansible-playbook -i "$INVENTORY" "${ANSIBLE_LIMIT_OPTS[@]-}" "$PLAYBOOK" --ask-pass -u "$SSH_KEY_USER" 2>&1 | tee "$TMP3"
|
||||
RC3=${PIPESTATUS[0]}
|
||||
set -e
|
||||
|
||||
if [[ $RC3 -eq 0 ]]; then
|
||||
echo
|
||||
echo "✅ Run successful (password auth as $SSH_KEY_USER)"
|
||||
rm -f "$TMP1" "$TMP2" "$TMP3"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "❌ All attempts failed."
|
||||
echo " Logs:"
|
||||
echo " - Attempt 1: $TMP1 (rc=$RC1)"
|
||||
echo " - Attempt 2: $TMP2 (rc=$RC2)"
|
||||
echo " - Attempt 3: $TMP3 (rc=$RC3)"
|
||||
exit 1
|
||||
else
|
||||
echo
|
||||
echo "❌ Attempts 1 & 2 failed; final user fallback disabled."
|
||||
echo " Logs:"
|
||||
echo " - Attempt 1: $TMP1 (rc=$RC1)"
|
||||
echo " - Attempt 2: $TMP2 (rc=$RC2)"
|
||||
exit 1
|
||||
fi
|
||||
27
templates/iptables/rules.v4.j2
Normal file
27
templates/iptables/rules.v4.j2
Normal file
@@ -0,0 +1,27 @@
|
||||
*filter
|
||||
:INPUT DROP [0:0]
|
||||
:FORWARD DROP [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
|
||||
# Always allow loopback
|
||||
-A INPUT -i lo -j ACCEPT
|
||||
|
||||
# Accept already established/related
|
||||
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Drop invalid early
|
||||
-A INPUT -m conntrack --ctstate INVALID -j DROP
|
||||
|
||||
# Allow TCP ports from vars
|
||||
{% for p in firewall_tcp_ports | default([]) %}
|
||||
-A INPUT -p tcp --dport {{ p }} -j ACCEPT
|
||||
{% endfor %}
|
||||
|
||||
# Allow UDP ports from vars
|
||||
{% for p in firewall_udp_ports | default([]) %}
|
||||
-A INPUT -p udp --dport {{ p }} -j ACCEPT
|
||||
{% endfor %}
|
||||
|
||||
# add further custom rules below
|
||||
|
||||
COMMIT
|
||||
Reference in New Issue
Block a user