chore: link vault wiki to Gitea

This commit is contained in:
2026-03-11 11:20:12 -04:00
parent fe6ec20351
commit 9c22a661ea
54 changed files with 3069 additions and 3 deletions

View File

View File

@@ -0,0 +1,208 @@
---
title: "Ansible Getting Started: Inventory, Playbooks, and Ad-Hoc Commands"
domain: linux
category: shell-scripting
tags: [ansible, automation, infrastructure, linux, idempotent]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Ansible Getting Started: Inventory, Playbooks, and Ad-Hoc Commands
Ansible is how I manage infrastructure at scale — or even just across a handful of machines. You write what you want the end state to look like, Ansible figures out how to get there. No agents needed on the remote machines, just SSH.
## The Short Answer
```bash
# Install Ansible
pip install ansible
# Run a one-off command on all hosts
ansible all -i inventory.ini -m ping
# Run a playbook
ansible-playbook -i inventory.ini site.yml
```
## Core Concepts
**Inventory** — the list of machines Ansible manages. Can be a static file or dynamically generated.
**Playbook** — a YAML file describing tasks to run on hosts. The main thing you write.
**Module** — the building blocks of tasks. `apt`, `dnf`, `service`, `copy`, `template`, `user`, etc. Ansible has modules for almost everything.
**Idempotency** — run the same playbook ten times, the result is the same as running it once. Ansible modules are designed this way. This matters because it means you can re-run playbooks safely without side effects.
## Inventory File
```ini
# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_user=admin
[databases]
db1.example.com ansible_user=ubuntu ansible_port=2222
[all:vars]
ansible_user=myuser
ansible_ssh_private_key_file=~/.ssh/id_ed25519
```
Test connectivity:
```bash
ansible all -i inventory.ini -m ping
```
A successful response looks like:
```
web1.example.com | SUCCESS => {
"ping": "pong"
}
```
## Ad-Hoc Commands
For quick one-offs without writing a playbook:
```bash
# Run a shell command
ansible all -i inventory.ini -m shell -a "uptime"
# Install a package
ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become
# Restart a service
ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" --become
# Copy a file
ansible all -i inventory.ini -m copy -a "src=./myfile dest=/tmp/myfile"
```
`--become` escalates to sudo.
## Writing a Playbook
```yaml
---
# site.yml
- name: Configure web servers
hosts: webservers
become: true
vars:
app_port: 8080
tasks:
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Start and enable nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Deploy config file
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
```
Run it:
```bash
ansible-playbook -i inventory.ini site.yml
# Dry run — shows what would change without doing it
ansible-playbook -i inventory.ini site.yml --check
# Verbose output
ansible-playbook -i inventory.ini site.yml -v
```
## Handlers
Handlers run at the end of a play, only if notified. The canonical use is "reload service after config change":
```yaml
tasks:
- name: Deploy config
ansible.builtin.template:
src: templates/app.conf.j2
dest: /etc/app/app.conf
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.service:
name: myapp
state: restarted
```
If the config file didn't change (idempotent — it was already in the right state), the notify never fires and the service isn't restarted.
## Roles
Once playbooks get complex, organize them into roles:
```
roles/
webserver/
tasks/
main.yml
handlers/
main.yml
templates/
nginx.conf.j2
defaults/
main.yml
```
Use a role in a playbook:
```yaml
- name: Set up web servers
hosts: webservers
become: true
roles:
- webserver
```
Roles keep things organized and reusable across projects.
## Gotchas & Notes
- **YAML indentation matters.** Two spaces is standard. Tab characters will break your playbooks.
- **`--check` is your friend.** Always dry-run against production before applying changes.
- **SSH key access is required.** Ansible connects over SSH — password auth works but key auth is what you want for automation.
- **`gather_facts: false`** speeds up playbooks when you don't need host facts (OS, IP, etc.). Add it at the play level for simple playbooks.
- **Ansible is not idempotent by magic.** Shell and command modules run every time regardless of state. Use the appropriate module (`apt`, `service`, `file`, etc.) instead of `shell` whenever possible.
- **The `ansible-lint` tool** catches common mistakes before they run. Worth adding to your workflow.
## See Also
- [[managing-linux-services-systemd-ansible]]
- [[linux-server-hardening-checklist]]

View File

@@ -0,0 +1,215 @@
---
title: "Bash Scripting Patterns for Sysadmins"
domain: linux
category: shell-scripting
tags: [bash, scripting, automation, linux, shell]
status: published
created: 2026-03-08
updated: 2026-03-08
---
# Bash Scripting Patterns for Sysadmins
These are the patterns I reach for when writing bash scripts for server automation and maintenance tasks. Not a tutorial from scratch — this assumes you know basic bash syntax and want to write scripts that don't embarrass you later.
## The Short Answer
```bash
#!/usr/bin/env bash
set -euo pipefail
```
Start every script with these two lines. `set -e` exits on error. `set -u` treats unset variables as errors. `set -o pipefail` catches errors in pipes. Together they prevent a lot of silent failures.
## Script Header
```bash
#!/usr/bin/env bash
set -euo pipefail
# ── Config ─────────────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
LOG_FILE="/var/log/myscript.log"
# ───────────────────────────────────────────────────────────────────────────────
```
## Logging
```bash
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
log "Script started"
log "Processing $1"
```
## Error Handling
```bash
# Exit with a message
die() {
echo "ERROR: $*" >&2
exit 1
}
# Check a condition
[ -f "$CONFIG_FILE" ] || die "Config file not found: $CONFIG_FILE"
# Trap to run cleanup on exit
cleanup() {
log "Cleaning up temp files"
rm -f "$TMPFILE"
}
trap cleanup EXIT
```
## Checking Dependencies
```bash
check_deps() {
local deps=("curl" "jq" "rsync")
for dep in "${deps[@]}"; do
command -v "$dep" &>/dev/null || die "Required dependency not found: $dep"
done
}
check_deps
```
## Argument Parsing
```bash
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [OPTIONS] <target>
Options:
-v, --verbose Enable verbose output
-n, --dry-run Show what would be done without doing it
-h, --help Show this help
EOF
exit 0
}
VERBOSE=false
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose) VERBOSE=true; shift ;;
-n|--dry-run) DRY_RUN=true; shift ;;
-h|--help) usage ;;
--) shift; break ;;
-*) die "Unknown option: $1" ;;
*) TARGET="$1"; shift ;;
esac
done
[[ -z "${TARGET:-}" ]] && die "Target is required"
```
## Running Commands
```bash
# Dry-run aware command execution
run() {
if $DRY_RUN; then
echo "DRY RUN: $*"
else
"$@"
fi
}
run rsync -av /source/ /dest/
run systemctl restart myservice
```
## Working with Files and Directories
```bash
# Check existence before use
[[ -d "$DIR" ]] || mkdir -p "$DIR"
[[ -f "$FILE" ]] || die "Expected file not found: $FILE"
# Safe temp files
TMPFILE="$(mktemp)"
trap 'rm -f "$TMPFILE"' EXIT
# Loop over files
find /path/to/files -name "*.log" -mtime +30 | while read -r file; do
log "Processing: $file"
run gzip "$file"
done
```
## String Operations
```bash
# Extract filename without extension
filename="${filepath##*/}" # basename
stem="${filename%.*}" # strip extension
# Check if string contains substring
if [[ "$output" == *"error"* ]]; then
die "Error detected in output"
fi
# Convert to lowercase
lower="${str,,}"
# Trim whitespace
trimmed="${str#"${str%%[![:space:]]*}"}"
```
## Common Patterns
**Backup with timestamp:**
```bash
backup() {
local source="$1"
local dest="${2:-/backup}"
local timestamp
timestamp="$(date '+%Y%m%d_%H%M%S')"
local backup_path="${dest}/$(basename "$source")_${timestamp}.tar.gz"
log "Backing up $source to $backup_path"
run tar -czf "$backup_path" -C "$(dirname "$source")" "$(basename "$source")"
}
```
**Retry on failure:**
```bash
retry() {
local max_attempts="${1:-3}"
local delay="${2:-5}"
shift 2
local attempt=1
until "$@"; do
if ((attempt >= max_attempts)); then
die "Command failed after $max_attempts attempts: $*"
fi
log "Attempt $attempt failed, retrying in ${delay}s..."
sleep "$delay"
((attempt++))
done
}
retry 3 10 curl -f https://example.com/health
```
## Gotchas & Notes
- **Always quote variables.** `"$var"` not `$var`. Unquoted variables break on spaces and glob characters.
- **Use `[[` not `[` for conditionals.** `[[` is a bash built-in with fewer edge cases.
- **`set -e` exits on the first error — including in pipes.** Add `set -o pipefail` or you'll miss failures in `cmd1 | cmd2`.
- **`$?` after `if` is almost always wrong.** Use `if command; then` not `command; if [[ $? -eq 0 ]]; then`.
- **Bash isn't great for complex data.** If your script needs real data structures or error handling beyond strings, consider Python.
## See Also
- [[ansible-getting-started]]
- [[managing-linux-services-systemd-ansible]]