Skip to main content
  1. Wiki/

Automation

Automation Pipeline

Automation is the backbone of my homelab. This wiki covers the tools, patterns, and workflows that keep 50+ services running with minimal manual intervention.

Automation Stack
#

Automation stack showing triggers, orchestration, and execution layers

n8n Workflow Automation
#

n8n is my primary workflow automation platform—think Zapier/Make but self-hosted with full code access.

Active Workflows
#

WorkflowTriggerPurpose
Proxmox MonitorWebhook (Discord bot)AI-assisted cluster management
UniFi ReportsSchedule (weekly)Network device inventory reports
Meeting NotesWebhookTranscription and summarization
Service AlertsWebhookRoute alerts to appropriate channels

n8n Architecture
#

n8n architecture with workflow engine, credentials store, and external services

Workflow Patterns
#

1. Event → Process → Notify

1
2
3
4
5
Trigger (webhook/schedule)
Process data (HTTP requests, code nodes)
Notify (Discord, email, webhook)

2. Fan-Out/Fan-In

1
2
3
4
5
6
7
8
9
    Single trigger
Split to parallel branches
 Process independently
     Merge results
     Final action

3. Error Handling

n8n error handling pattern with try node routing to success or Discord alert

Discord Bot Integration
#

A Discord bot forwards DMs and @mentions to n8n webhooks, enabling conversational automation:

1
2
3
4
5
6
7
8
9
   Discord @mention
Discord Bot (LXC container)
      n8n Webhook
AI Processing (Claude API)
  Response → Discord

Use cases:

  • “Check Proxmox cluster status”
  • “List running VMs on node 3”
  • “Show recent Graylog alerts”

Semaphore CI/CD
#

Semaphore is my Ansible automation platform—a web UI for running playbooks with scheduling and audit logging.

Active Playbooks
#

CategoryPlaybooksPurpose
Caddy Managementadd-domain, remove-domain, list-domainsReverse proxy automation
Power Managementnight-sleep, day-onProxmox cluster power scheduling
Maintenanceupdate-containers, backup-verifyRoutine maintenance tasks

Caddy Domain Automation
#

Instead of SSH’ing to both Caddy nodes manually, Semaphore playbooks handle domain management:

Add Domain:

  1. Input: domain name, backend IP, port
  2. Playbook generates Caddy config snippet
  3. Deploys to BOTH nodes (prevents config drift)
  4. Validates with caddy validate
  5. Reloads Caddy

Remove Domain:

  1. Input: domain name
  2. Playbook removes from both nodes
  3. Validates and reloads

List Domains:

List of configured domains managed by Semaphore

Power Management Automation
#

Scheduled playbooks manage Proxmox cluster power:

SchedulePlaybookAction
11 PMnight-sleepShutdown non-essential VMs/LXCs
7 AMday-onWake up daytime services

Why automate power?

  • Reduce electricity costs
  • Extend hardware lifespan
  • Lower heat output
  • Essential services (DNS, proxy, backups) stay running 24/7

Important gotcha: Semaphore’s cron scheduler ignores the container’s TZ variable. Must set SEMAPHORE_SCHEDULE_TIMEZONE explicitly.

Scripting Standards
#

Bash Scripts
#

All bash scripts follow these conventions:

1
2
3
4
5
#!/usr/bin/env bash
set -euo pipefail  # Exit on error, undefined vars, pipe failures

# Trap for cleanup
trap 'echo "Error on line $LINENO"; exit 1' ERR

Validation:

  • shellcheck for linting
  • shfmt for formatting
  • Tested on both Ubuntu and Debian

Python Scripts
#

For complex automation, Python with type hints:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env python3
"""Script description."""

from pathlib import Path
import argparse
import logging

def main() -> int:
    """Entry point."""
    ...
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

Tooling:

  • ruff for linting
  • mypy for type checking
  • uv for fast package management

Backup Automation
#

Standardized backup pattern across services:

Standardized backup pattern: script, lock, archive, NFS, retention, verify, Discord

Services with automated backups:

  • Vaultwarden (password manager)
  • n8n workflows
  • Graylog configuration
  • Pi-hole settings

Infrastructure as Code
#

Git-Based Deployment
#

All configurations live in Git repositories:

Infrastructure as Code repository structure

Deployment flow:

  1. Edit config locally
  2. Commit and push
  3. Portainer/Semaphore pulls changes
  4. Service reloads

Portainer GitOps
#

Docker Compose stacks deploy via Portainer’s Git integration:

Portainer GitOps pipeline: GitHub → Portainer → Compose → Containers

Update process:

  1. Push to main branch
  2. Portainer detects change (webhook or poll)
  3. Stack redeployed automatically

WUD (What’s Up Docker) Update Monitoring
#

All Docker services include WUD for opt-in update monitoring (replaced Watchtower):

1
2
3
4
5
6
7
8
9
wud:
  image: getwud/wud:latest
  environment:
    - WUD_WATCHER_LOCAL_WATCHBYDEFAULT=false
    - WUD_TRIGGER_DISCORD_URL=discord://<WEBHOOK_ID>/<WEBHOOK_TOKEN>
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
  labels:
    - wud.watch=true

Key difference from Watchtower: WUD uses an opt-in model — only containers with wud.watch=true are monitored. WUD notifies about available updates but does not auto-update, giving you control over when to apply changes.

Key settings:

  • WATCHBYDEFAULT=false: Only watch labeled containers (opt-in)
  • Discord notifications when new versions are available
  • Web UI dashboard showing update status across all services

Notification Patterns
#

Discord Webhook Embeds
#

All automation sends Discord notifications with consistent formatting:

Discord webhook embed with status, duration, and timestamp

Color coding:

  • 🟢 Green: Success
  • 🟡 Yellow: Warning
  • 🔴 Red: Error/Fault

Shoutrrr Integration
#

WUD and other services use Shoutrrr for notifications:

1
discord://<webhook_id>/<webhook_token>?color=5793266&title=ServiceName

Lessons Learned
#

1. Idempotency is Non-Negotiable
#

Every script must be safe to run multiple times:

  • Check before creating (id -u user before useradd)
  • Use --ignore-existing flags where available
  • Test with set -e to catch hidden failures

2. Timezone Chaos
#

Different tools handle timezones differently:

  • Docker: Uses host timezone or TZ env var
  • Cron: Uses container timezone
  • Semaphore: Has its OWN SEMAPHORE_SCHEDULE_TIMEZONE

Always set timezone explicitly. Default: America/Los_Angeles.

3. Lock Files Prevent Disasters
#

Long-running scripts need lock files:

1
2
3
LOCK_FILE="/var/lock/backup.lock"
exec 9>"$LOCK_FILE"
flock -n 9 || { echo "Already running"; exit 1; }

Without this, cron overlap caused corrupted backups.

4. Validate Before Deploy
#

Every deployment includes validation:

  • docker compose config --quiet before deploy
  • caddy validate before reload
  • bash -n script.sh before execution

Related Pages#

2026

Architecture: Caddy GitOps Auto-Deploy

Overview # After multiple outages caused by configuration drift between two HA Caddy reverse proxy nodes, I built a GitOps pipeline that automatically deploys configs to both nodes whenever changes are pushed to the main branch. Config drift is now impossible by design. The problem: Two Caddy nodes in a keepalived HA pair need identical configs. Forgetting to deploy to the second node after editing a site config caused service outages — twice in the same week.

2025

Architecture: Homelab Journal Site Structure

Overview # This site uses a hybrid content structure that combines the best of wikis and blogs. Instead of choosing between “everything chronological” or “everything by topic,” we get both: Wiki sections for evergreen reference content (organized by topic) Blog posts for journey updates and lessons learned (organized by date) Tutorials for step-by-step guides (standalone, searchable) Series for multi-part deep dives (linked learning paths) Content Architecture #