Reorganize repos into kitestacks-homelab + plain-English doc rewrite
- Rewrote RUNBOOK.md and DEBUG-DOCUMENTATION.md in simple 5th-grade language with real-world analogies for every technical concept - Updated README.md with current service inventory and folder map - Added cloud-migration/ subdirectory (from kitestacks-cloud-migration repo) - Added autosync/ subdirectory (from kitestacks-homelab-autosync-test repo) - Added osticket/ subdirectory (from OSTicketSystem repo) - Added cloud/ placeholder for future cloud configs - Excluded binary DB/postgres files from autosync subdirectory Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f79478158d
commit
fb822d5142
75 changed files with 11711 additions and 338 deletions
319
autosync/scripts/kitestacks-watcher-sh
Normal file
319
autosync/scripts/kitestacks-watcher-sh
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# kitestacks-watcher.sh
|
||||
# Runs as a systemd service. Watches configured directories, then on change:
|
||||
# 1. Pulls latest from Forgejo (avoid conflicts)
|
||||
# 2. Copies changed files into the correct apps/ or clusters/ folder
|
||||
# 3. Bumps the version in README.md and the docs/ changelog file
|
||||
# 4. Commits with a descriptive message
|
||||
# 5. Pushes to active repo (test first, then main after promote.sh)
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="$SCRIPT_DIR/../config/settings.conf"
|
||||
STATE_FILE="/opt/kitestacks-autosync/.active_target"
|
||||
WORK_DIR="/opt/kitestacks-autosync"
|
||||
|
||||
log() { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') INFO $*"; }
|
||||
warn() { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') WARN $*"; }
|
||||
err() { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') ERROR $*" >&2; }
|
||||
|
||||
# ── Load config ──────────────────────────────────────────────────────────────
|
||||
[[ ! -f "$CONFIG_FILE" ]] && { err "Config not found: $CONFIG_FILE"; exit 1; }
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
active_target() { cat "$STATE_FILE" 2>/dev/null || echo "test"; }
|
||||
|
||||
active_repo() {
|
||||
[[ "$(active_target)" == "main" ]] && echo "$MAIN_REPO" || echo "$TEST_REPO"
|
||||
}
|
||||
|
||||
repo_dir() { echo "$WORK_DIR/$(active_repo)"; }
|
||||
|
||||
# ── Version management ────────────────────────────────────────────────────────
|
||||
read_version() {
|
||||
local readme="$(repo_dir)/README.md"
|
||||
# Look for a line like: <!-- version: 1.3.2 --> or **Version:** 1.3.2
|
||||
local ver
|
||||
ver=$(grep -oP '(?<=version:\s)[\d.]+' "$readme" 2>/dev/null | head -1)
|
||||
[[ -z "$ver" ]] && ver=$(grep -oP '\d+\.\d+\.\d+' "$readme" 2>/dev/null | head -1)
|
||||
echo "${ver:-$VERSION_SEED}"
|
||||
}
|
||||
|
||||
bump_version() {
|
||||
local ver="$1"
|
||||
IFS='.' read -ra p <<< "$ver"
|
||||
local maj="${p[0]:-1}" min="${p[1]:-3}" pat="${p[2]:-0}"
|
||||
case "${VERSION_BUMP:-patch}" in
|
||||
major) echo "$((maj+1)).0.0" ;;
|
||||
minor) echo "${maj}.$((min+1)).0" ;;
|
||||
*) echo "${maj}.${min}.$((pat+1))" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── README.md update ──────────────────────────────────────────────────────────
|
||||
# Maintains the KiteStacks README.md format with version tracking.
|
||||
update_readme() {
|
||||
local dir="$1" new_ver="$2" old_ver="$3"
|
||||
local readme="$dir/README.md"
|
||||
local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
# If version comment tag exists, update it; otherwise append one after title
|
||||
if grep -q '<!-- version:' "$readme" 2>/dev/null; then
|
||||
sed -i "s|<!-- version:.*-->|<!-- version: $new_ver -->|" "$readme"
|
||||
else
|
||||
# Inject version tag after the first H1 line
|
||||
sed -i "0,/^# /{s|^# \(.*\)|# \1\n\n<!-- version: $new_ver -->|}" "$readme"
|
||||
fi
|
||||
|
||||
log "README.md version tag: $old_ver → $new_ver"
|
||||
}
|
||||
|
||||
# ── Docs changelog update ─────────────────────────────────────────────────────
|
||||
# Updates docs/KiteStacks-Homelab-Documentation-v<version>.md
|
||||
# Creates a new versioned doc file and a CHANGELOG.md entry.
|
||||
update_docs() {
|
||||
local dir="$1" new_ver="$2" changed_files="$3"
|
||||
local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
local docs_dir="$dir/docs"
|
||||
mkdir -p "$docs_dir"
|
||||
|
||||
# ── New versioned doc file ────────────────────────────────────────────────
|
||||
local doc_file="$docs_dir/KiteStacks-Homelab-Documentation-v${new_ver}.md"
|
||||
cat > "$doc_file" <<EOF
|
||||
# KiteStacks Homelab Documentation v${new_ver}
|
||||
|
||||
**Version:** ${new_ver}
|
||||
**Updated:** ${ts}
|
||||
**Previous:** [v${2%.*}.$(( ${new_ver##*.} - 1 )) docs](KiteStacks-Homelab-Documentation-v${2%.*}.$(( ${new_ver##*.} - 1 )).md)
|
||||
|
||||
---
|
||||
|
||||
## Change Summary
|
||||
|
||||
$(echo "$changed_files" | sed 's/^/- /')
|
||||
|
||||
---
|
||||
|
||||
## Cluster
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| K3s | Active |
|
||||
| FluxCD | Planned |
|
||||
| Longhorn | Planned |
|
||||
|
||||
## Applications
|
||||
|
||||
| App | Path |
|
||||
|-----|------|
|
||||
| Homepage | apps/homepage/ |
|
||||
| Kavita | apps/kavita-docker-automation/ |
|
||||
| Linkding | apps/linkding/ |
|
||||
| Forgejo | apps/forgejo/ |
|
||||
| Grafana | apps/grafana/ |
|
||||
| Prometheus | apps/prometheus/ |
|
||||
| Authentik | apps/authentik/ |
|
||||
| Open WebUI | apps/open-webui/ |
|
||||
| LiteLLM | apps/litellm/ |
|
||||
|
||||
## All Documentation Versions
|
||||
|
||||
$(ls "$docs_dir"/KiteStacks-Homelab-Documentation-v*.md 2>/dev/null \
|
||||
| sort -V \
|
||||
| while read f; do
|
||||
v=$(basename "$f" .md | grep -oP '[\d.]+$')
|
||||
echo "- [v${v}]($(basename "$f"))"
|
||||
done)
|
||||
EOF
|
||||
|
||||
log "Created doc file: $(basename "$doc_file")"
|
||||
|
||||
# ── CHANGELOG.md ─────────────────────────────────────────────────────────
|
||||
local changelog="$dir/CHANGELOG.md"
|
||||
if [[ ! -f "$changelog" ]]; then
|
||||
echo -e "# Changelog\n\nAll notable changes to KiteStacks Homelab are documented here.\n" > "$changelog"
|
||||
fi
|
||||
|
||||
# Prepend new entry after the header (after line 3)
|
||||
local entry
|
||||
entry=$(cat <<EOF
|
||||
|
||||
## [v${new_ver}] — ${ts}
|
||||
|
||||
### Changed
|
||||
$(echo "$changed_files" | sed 's/^/- /')
|
||||
|
||||
EOF
|
||||
)
|
||||
# Insert after line 3 of the changelog
|
||||
local tmp; tmp=$(mktemp)
|
||||
head -3 "$changelog" > "$tmp"
|
||||
echo "$entry" >> "$tmp"
|
||||
tail -n +4 "$changelog" >> "$tmp"
|
||||
mv "$tmp" "$changelog"
|
||||
|
||||
log "CHANGELOG.md updated with v${new_ver}"
|
||||
}
|
||||
|
||||
# ── Map a filesystem path to a repo subfolder ─────────────────────────────────
|
||||
# Files from ~/docker/homepage → apps/homepage/
|
||||
# Files from ~/docker/ → apps/<dirname>/
|
||||
# Files from /etc/kitestacks/ → clusters/assassin/
|
||||
map_to_repo_path() {
|
||||
local src="$1"
|
||||
local rel=""
|
||||
|
||||
for watch in $WATCH_DIRS; do
|
||||
if [[ "$src" == "$watch"* ]]; then
|
||||
rel="${src#$watch/}"
|
||||
# Top-level dir under watch becomes the app folder
|
||||
local top; top=$(echo "$rel" | cut -d'/' -f1)
|
||||
if [[ "$watch" == *docker* ]]; then
|
||||
echo "apps/${rel}"
|
||||
else
|
||||
echo "clusters/assassin/${rel}"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
done
|
||||
# Fallback
|
||||
echo "server-files/${src#/}"
|
||||
}
|
||||
|
||||
# ── Sync changed files into the workspace ─────────────────────────────────────
|
||||
sync_files() {
|
||||
local repo="$1"; shift
|
||||
local files=("$@")
|
||||
local synced=()
|
||||
|
||||
for src in "${files[@]}"; do
|
||||
local dest_rel; dest_rel="$(map_to_repo_path "$src")"
|
||||
local dest="$repo/$dest_rel"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
|
||||
if [[ -f "$src" ]]; then
|
||||
cp -p "$src" "$dest"
|
||||
synced+=("$dest_rel")
|
||||
log "Synced: $dest_rel"
|
||||
else
|
||||
rm -f "$dest"
|
||||
synced+=("DELETED: $dest_rel")
|
||||
log "Removed: $dest_rel"
|
||||
fi
|
||||
done
|
||||
|
||||
printf '%s\n' "${synced[@]}"
|
||||
}
|
||||
|
||||
# ── Commit and push ───────────────────────────────────────────────────────────
|
||||
commit_and_push() {
|
||||
local dir="$1" version="$2" file_count="$3"
|
||||
cd "$dir"
|
||||
|
||||
git add -A
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
log "Nothing to commit."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local target; target="$(active_target)"
|
||||
local msg="Automated update: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
# Match the commit style already in your repo: "Automated update: YYYY-MM-DD HH:MM:SS"
|
||||
|
||||
git commit -m "$msg"
|
||||
|
||||
log "Pushing to ${target} repo ($(active_repo))..."
|
||||
if git push origin HEAD 2>&1; then
|
||||
log "✓ Push OK — v${version}"
|
||||
else
|
||||
err "Push failed. Will retry on next change."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Should this path be skipped? ─────────────────────────────────────────────
|
||||
is_excluded() {
|
||||
local path="$1"
|
||||
for pat in $EXCLUDE_PATTERNS; do
|
||||
case "$path" in $pat) return 0 ;; esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ════════════════════════════════════════════════════════════════════════════
|
||||
mkdir -p "$WORK_DIR"
|
||||
log "KiteStacks AutoSync starting up"
|
||||
log "Active target : $(active_target) → $(active_repo)"
|
||||
log "Watching : $WATCH_DIRS"
|
||||
log "Debounce : ${DEBOUNCE_SECONDS}s"
|
||||
|
||||
# Pull latest before we start watching
|
||||
RDIR="$(repo_dir)"
|
||||
if [[ -d "$RDIR/.git" ]]; then
|
||||
cd "$RDIR"
|
||||
git pull --rebase origin HEAD 2>/dev/null && log "Pulled latest from remote." || warn "Pull failed — continuing anyway."
|
||||
fi
|
||||
|
||||
declare -A PENDING
|
||||
LAST_EVENT=0
|
||||
|
||||
# Build the inotifywait argument list from WATCH_DIRS
|
||||
IFS=' ' read -ra WATCH_ARRAY <<< "$WATCH_DIRS"
|
||||
|
||||
inotifywait -m -r \
|
||||
-e modify,create,delete,moved_to,moved_from \
|
||||
--format '%w%f' \
|
||||
--exclude '\.git' \
|
||||
"${WATCH_ARRAY[@]}" 2>/dev/null |
|
||||
|
||||
while IFS= read -r changed; do
|
||||
|
||||
is_excluded "$changed" && continue
|
||||
PENDING["$changed"]=1
|
||||
LAST_EVENT=$(date +%s)
|
||||
|
||||
# ── Debounce loop ─────────────────────────────────────────────────────────
|
||||
while true; do
|
||||
sleep 1
|
||||
ELAPSED=$(( $(date +%s) - LAST_EVENT ))
|
||||
[[ $ELAPSED -ge ${DEBOUNCE_SECONDS:-15} ]] && break
|
||||
|
||||
# Drain any additional events that arrived during sleep
|
||||
while IFS= read -r -t 0.1 extra; do
|
||||
is_excluded "$extra" && continue
|
||||
PENDING["$extra"]=1
|
||||
LAST_EVENT=$(date +%s)
|
||||
done
|
||||
done
|
||||
|
||||
[[ ${#PENDING[@]} -eq 0 ]] && continue
|
||||
|
||||
log "━━ Batch of ${#PENDING[@]} change(s) detected ━━"
|
||||
|
||||
RDIR="$(repo_dir)"
|
||||
OLD_VER="$(read_version)"
|
||||
NEW_VER="$(bump_version "$OLD_VER")"
|
||||
|
||||
# Pull before committing
|
||||
cd "$RDIR"
|
||||
git pull --rebase origin HEAD 2>/dev/null || warn "Pre-commit pull failed."
|
||||
|
||||
# Sync files and capture the list of repo-relative paths
|
||||
CHANGED_LIST="$(sync_files "$RDIR" "${!PENDING[@]}")"
|
||||
|
||||
update_readme "$RDIR" "$NEW_VER" "$OLD_VER"
|
||||
update_docs "$RDIR" "$NEW_VER" "$CHANGED_LIST"
|
||||
commit_and_push "$RDIR" "$NEW_VER" "${#PENDING[@]}"
|
||||
|
||||
# Clear batch
|
||||
unset PENDING
|
||||
declare -A PENDING
|
||||
|
||||
done
|
||||
Loading…
Add table
Add a link
Reference in a new issue