Add autosync scripts
This commit is contained in:
parent
6aed620678
commit
d5b00d9c79
5 changed files with 900 additions and 18 deletions
164
README.md
164
README.md
|
|
@ -1,25 +1,157 @@
|
|||
╔══════════════════════════════════════════════════════════════════╗
|
||||
║ KiteStacks Homelab — Auto-Sync Setup Guide ║
|
||||
╚══════════════════════════════════════════════════════════════════╝
|
||||
|
||||
WHAT THIS DOES
|
||||
──────────────
|
||||
Watches your Docker app directories for any file changes.
|
||||
On each change it automatically:
|
||||
|
||||
1. Copies the changed file into the correct repo folder
|
||||
~/docker/homepage/ → apps/homepage/
|
||||
~/docker/<app>/ → apps/<app>/
|
||||
/etc/kitestacks/ → clusters/assassin/
|
||||
|
||||
2. Bumps the version in README.md
|
||||
<!-- version: 1.3.2 --> → <!-- version: 1.3.3 -->
|
||||
|
||||
3. Creates a new versioned doc file
|
||||
docs/KiteStacks-Homelab-Documentation-v1.3.3.md
|
||||
|
||||
4. Appends an entry to CHANGELOG.md
|
||||
|
||||
5. Commits with your existing style:
|
||||
"Automated update: 2026-06-06 03:36:00"
|
||||
|
||||
6. Pushes to Forgejo at gitforge.kitestacks.com
|
||||
|
||||
It pushes to a TEST repo first so you can verify before
|
||||
flipping to kenpat/kitestacks-homelab.
|
||||
|
||||
|
||||
FILES
|
||||
─────
|
||||
setup.sh ← Run first (as root)
|
||||
promote.sh ← Run to switch test → main repo
|
||||
status.sh ← Health check
|
||||
config/
|
||||
settings.conf.example
|
||||
settings.conf ← Your config (fill this in first)
|
||||
scripts/
|
||||
kitestacks-watcher.sh ← The daemon (started by systemd)
|
||||
|
||||
|
||||
STEP-BY-STEP
|
||||
════════════
|
||||
|
||||
1. GET A FORGEJO TOKEN
|
||||
gitforge.kitestacks.com → User Settings → Applications
|
||||
→ Generate New Token → name it "autosync"
|
||||
→ Scopes: ✓ repository (read + write)
|
||||
Copy it — you only see it once.
|
||||
|
||||
2. CONFIGURE
|
||||
cp config/settings.conf.example config/settings.conf
|
||||
nano config/settings.conf
|
||||
|
||||
Required changes:
|
||||
FORGEJO_TOKEN="your-token-here"
|
||||
WATCH_DIRS="/home/kenpat/docker /etc/kitestacks"
|
||||
↑ adjust to match where your app files live
|
||||
|
||||
3. RUN SETUP
|
||||
sudo bash setup.sh
|
||||
|
||||
This will:
|
||||
• Install git, inotify-tools, curl, jq
|
||||
• Create kitestacks-homelab-autosync-test on Forgejo
|
||||
• Clone it to /opt/kitestacks-autosync/
|
||||
• Bootstrap README.md and docs/ at v1.3.0
|
||||
• Install and start the systemd service
|
||||
|
||||
4. VERIFY
|
||||
bash status.sh
|
||||
journalctl -u kitestacks-autosync -f
|
||||
|
||||
Trigger a test change:
|
||||
touch /home/kenpat/docker/homepage/test-autosync.txt
|
||||
|
||||
Check gitforge.kitestacks.com/kenpat/kitestacks-homelab-autosync-test
|
||||
You should see:
|
||||
• A new commit: "Automated update: 2026-06-06 ..."
|
||||
• apps/homepage/test-autosync.txt added
|
||||
• README.md version bumped: 1.3.0 → 1.3.1
|
||||
• docs/KiteStacks-Homelab-Documentation-v1.3.1.md created
|
||||
• CHANGELOG.md entry added
|
||||
|
||||
Clean up the test file:
|
||||
rm /home/kenpat/docker/homepage/test-autosync.txt
|
||||
|
||||
5. PROMOTE TO MAIN REPO
|
||||
sudo bash promote.sh
|
||||
|
||||
Type "promote" to confirm. From this point on, all changes
|
||||
go to kenpat/kitestacks-homelab.
|
||||
|
||||
|
||||
HOW THE README.md VERSIONING WORKS
|
||||
════════════════════════════════════
|
||||
The script tracks versions using an HTML comment tag that is
|
||||
invisible when the README renders:
|
||||
|
||||
# KiteStacks Homelab
|
||||
|
||||
<!-- version: 1.3.4 -->
|
||||
|
||||
Private GitOps repository for the KiteStacks homelab.
|
||||
...
|
||||
|
||||
## Cluster
|
||||
This tag is injected automatically on first run (it won't
|
||||
change how your README looks in the browser).
|
||||
|
||||
- K3s
|
||||
- FluxCD (planned)
|
||||
- Longhorn (planned)
|
||||
The docs/ reference line is also kept current:
|
||||
docs/KiteStacks-Homelab-Documentation-v1.3.4.md
|
||||
|
||||
## Applications
|
||||
|
||||
- Homepage
|
||||
- Kavita
|
||||
- Linkding
|
||||
- Forgejo
|
||||
- Grafana
|
||||
- Prometheus
|
||||
- Authentik
|
||||
- Open WebUI
|
||||
- LiteLLM
|
||||
DIRECTORY MAPPING
|
||||
═════════════════
|
||||
When a file changes, it's placed in the repo like this:
|
||||
|
||||
## Documentation
|
||||
Server path Repo path
|
||||
───────────────────────────────── ─────────────────────────────────
|
||||
~/docker/homepage/config/… apps/homepage/config/…
|
||||
~/docker/kavita-docker-automation/… apps/kavita-docker-automation/…
|
||||
/etc/kitestacks/… clusters/assassin/…
|
||||
|
||||
docs/KiteStacks-Homelab-Documentation-v1.2.md
|
||||
|
||||
MANAGING THE SERVICE
|
||||
════════════════════
|
||||
bash status.sh # health check
|
||||
journalctl -u kitestacks-autosync -f # live logs
|
||||
sudo systemctl restart kitestacks-autosync # restart
|
||||
sudo systemctl stop kitestacks-autosync # stop
|
||||
|
||||
|
||||
DEBOUNCE
|
||||
════════
|
||||
Default: 15 seconds after the last change before committing.
|
||||
This means if you save 10 files in 5 seconds, you get ONE commit
|
||||
not 10. Adjust DEBOUNCE_SECONDS in settings.conf.
|
||||
|
||||
|
||||
TROUBLESHOOTING
|
||||
═══════════════
|
||||
Push fails with auth error
|
||||
→ Check FORGEJO_TOKEN is correct in settings.conf
|
||||
→ Re-run: sudo bash setup.sh
|
||||
|
||||
"Config not found" on start
|
||||
→ sudo bash setup.sh (it will copy the example for you)
|
||||
|
||||
Service crashes
|
||||
→ journalctl -u kitestacks-autosync -n 50
|
||||
→ Check WATCH_DIRS path(s) exist
|
||||
|
||||
Files not appearing in apps/ subfolder
|
||||
→ Check WATCH_DIRS in settings.conf matches your actual
|
||||
Docker compose directory (e.g. /home/kenpat/docker)
|
||||
|
|
|
|||
102
promote.sh
Normal file
102
promote.sh
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# promote.sh
|
||||
# Flips the watcher from TEST repo → kenpat/kitestacks-homelab (main).
|
||||
# Run after verifying the test repo looks correct in Forgejo.
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
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"
|
||||
|
||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
|
||||
[[ $EUID -ne 0 ]] && error "Run as root: sudo bash promote.sh"
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
CURRENT="$(cat "$STATE_FILE" 2>/dev/null || echo "test")"
|
||||
if [[ "$CURRENT" == "main" ]]; then
|
||||
warn "Already targeting kenpat/kitestacks-homelab. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
warn "┌──────────────────────────────────────────────────────────────┐"
|
||||
warn "│ PROMOTE: test repo → kenpat/kitestacks-homelab │"
|
||||
warn "│ │"
|
||||
warn "│ From: ${FORGEJO_URL}/${FORGEJO_USER}/${TEST_REPO}"
|
||||
warn "│ To : ${FORGEJO_URL}/${FORGEJO_USER}/${MAIN_REPO}"
|
||||
warn "│ │"
|
||||
warn "│ All future auto-commits will go to the MAIN repo. │"
|
||||
warn "└──────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
read -rp "Have you verified the test repo looks correct? Type 'promote' to confirm: " CONFIRM
|
||||
[[ "$CONFIRM" != "promote" ]] && { info "Aborted."; exit 0; }
|
||||
|
||||
# ── Set up remote prefix ──────────────────────────────────────────────────────
|
||||
if [[ "$AUTH_METHOD" == "ssh" ]]; then
|
||||
REMOTE_BASE="git@gitforge.kitestacks.com:${FORGEJO_USER}"
|
||||
else
|
||||
REMOTE_BASE="https://gitforge.kitestacks.com/${FORGEJO_USER}"
|
||||
fi
|
||||
|
||||
# ── Clone main repo if not already present ────────────────────────────────────
|
||||
MAIN_DIR="$WORK_DIR/$MAIN_REPO"
|
||||
if [[ ! -d "$MAIN_DIR/.git" ]]; then
|
||||
info "Cloning kenpat/kitestacks-homelab..."
|
||||
git clone "${REMOTE_BASE}/${MAIN_REPO}.git" "$MAIN_DIR"
|
||||
fi
|
||||
|
||||
cd "$MAIN_DIR"
|
||||
git config user.email "$GIT_EMAIL"
|
||||
git config user.name "$GIT_NAME"
|
||||
git pull --rebase origin HEAD 2>/dev/null || true
|
||||
|
||||
# ── Add version tag to main README.md if not already there ───────────────────
|
||||
if ! grep -q '<!-- version:' "$MAIN_DIR/README.md" 2>/dev/null; then
|
||||
info "Injecting version tag into main README.md..."
|
||||
|
||||
# Get the latest version from the test repo
|
||||
TEST_VER=$(grep -oP '(?<=version:\s)[\d.]+' "$WORK_DIR/$TEST_REPO/README.md" 2>/dev/null | head -1 || echo "1.3.0")
|
||||
|
||||
# Inject after the first H1
|
||||
sed -i "0,/^# /{s|^# \(.*\)|# \1\n\n<!-- version: $TEST_VER -->|}" "$MAIN_DIR/README.md"
|
||||
|
||||
# Update the docs reference line
|
||||
sed -i "s|docs/KiteStacks-Homelab-Documentation-v.*\.md|docs/KiteStacks-Homelab-Documentation-v${TEST_VER}.md|" "$MAIN_DIR/README.md" || true
|
||||
|
||||
# Copy CHANGELOG.md and docs from test repo if main doesn't have them
|
||||
if [[ ! -f "$MAIN_DIR/CHANGELOG.md" ]]; then
|
||||
cp "$WORK_DIR/$TEST_REPO/CHANGELOG.md" "$MAIN_DIR/CHANGELOG.md" 2>/dev/null || true
|
||||
fi
|
||||
if [[ ! -d "$MAIN_DIR/docs" ]]; then
|
||||
cp -r "$WORK_DIR/$TEST_REPO/docs" "$MAIN_DIR/docs" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: inject autosync version tracking — promoted from test"
|
||||
git push origin HEAD
|
||||
info "Main repo prepared for autosync."
|
||||
fi
|
||||
|
||||
# ── Flip state ────────────────────────────────────────────────────────────────
|
||||
echo "main" > "$STATE_FILE"
|
||||
info "Active target → main (kenpat/kitestacks-homelab)"
|
||||
|
||||
# ── Restart service ───────────────────────────────────────────────────────────
|
||||
info "Restarting kitestacks-autosync service..."
|
||||
systemctl restart kitestacks-autosync
|
||||
sleep 2
|
||||
systemctl status kitestacks-autosync --no-pager
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
info "Promotion complete!"
|
||||
info " Now watching → ${FORGEJO_URL}/${FORGEJO_USER}/${MAIN_REPO}"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
319
scripts/kitestacks-watcher-sh
Normal file
319
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
|
||||
244
setup.sh
Normal file
244
setup.sh
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# KiteStacks HomelaB — Auto-Sync Setup
|
||||
# Run once as root: sudo bash setup.sh
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="$SCRIPT_DIR/config/settings.conf"
|
||||
WORK_DIR="/opt/kitestacks-autosync"
|
||||
STATE_FILE="$WORK_DIR/.active_target"
|
||||
|
||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
step() { echo -e "\n${CYAN}══ $1 ══${NC}"; }
|
||||
|
||||
[[ $EUID -ne 0 ]] && error "Run as root: sudo bash setup.sh"
|
||||
|
||||
# ── Config check ─────────────────────────────────────────────────────────────
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
info "Copying example config..."
|
||||
cp "$SCRIPT_DIR/config/settings.conf.example" "$CONFIG_FILE"
|
||||
warn "Please edit $CONFIG_FILE and set FORGEJO_TOKEN and WATCH_DIRS, then re-run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
[[ "$FORGEJO_TOKEN" == "PASTE_YOUR_TOKEN_HERE" ]] && \
|
||||
error "You haven't set FORGEJO_TOKEN in config/settings.conf yet."
|
||||
[[ -z "$FORGEJO_TOKEN" ]] && error "FORGEJO_TOKEN is empty in config/settings.conf."
|
||||
|
||||
# ── Dependencies ─────────────────────────────────────────────────────────────
|
||||
step "Installing dependencies"
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq git inotify-tools curl jq
|
||||
info "Dependencies installed."
|
||||
|
||||
# ── Configure git credentials ─────────────────────────────────────────────────
|
||||
step "Configuring git credentials"
|
||||
if [[ "$AUTH_METHOD" == "ssh" ]]; then
|
||||
if [[ ! -f "$SSH_KEY_PATH" ]]; then
|
||||
info "Generating SSH key at $SSH_KEY_PATH..."
|
||||
ssh-keygen -t ed25519 -C "kitestacks-autosync@$(hostname)" -f "$SSH_KEY_PATH" -N ""
|
||||
echo ""
|
||||
warn "Add this public key to Forgejo:"
|
||||
warn " gitforge.kitestacks.com → User Settings → SSH / GPG Keys → Add Key"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
cat "${SSH_KEY_PATH}.pub"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
read -rp "Press Enter after adding the key to Forgejo..."
|
||||
fi
|
||||
REMOTE_BASE="git@gitforge.kitestacks.com:${FORGEJO_USER}"
|
||||
else
|
||||
# HTTPS token auth
|
||||
git config --global credential.helper store
|
||||
# Write credential (idempotent — grep prevents duplicates)
|
||||
local CRED_LINE="https://${FORGEJO_USER}:${FORGEJO_TOKEN}@gitforge.kitestacks.com"
|
||||
grep -qF "$CRED_LINE" ~/.git-credentials 2>/dev/null || echo "$CRED_LINE" >> ~/.git-credentials
|
||||
REMOTE_BASE="https://gitforge.kitestacks.com/${FORGEJO_USER}"
|
||||
fi
|
||||
info "Credentials configured (method: $AUTH_METHOD)"
|
||||
|
||||
# ── Verify Forgejo API ────────────────────────────────────────────────────────
|
||||
step "Verifying Forgejo connectivity"
|
||||
HTTP=$(curl -s -o /dev/null -w "%{http_code}" --max-time 8 \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"${FORGEJO_URL}/api/v1/user")
|
||||
[[ "$HTTP" != "200" ]] && error "Forgejo API returned HTTP $HTTP — check FORGEJO_URL and FORGEJO_TOKEN."
|
||||
info "Forgejo API reachable ✓"
|
||||
|
||||
# ── Create test repo on Forgejo if needed ─────────────────────────────────────
|
||||
step "Setting up test repo: $TEST_REPO"
|
||||
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"${FORGEJO_URL}/api/v1/repos/${FORGEJO_USER}/${TEST_REPO}")
|
||||
|
||||
if [[ "$HTTP" == "404" ]]; then
|
||||
info "Creating test repo on Forgejo..."
|
||||
RESULT=$(curl -s -X POST \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"${TEST_REPO}\",
|
||||
\"description\": \"AutoSync test — KiteStacks Homelab\",
|
||||
\"private\": true,
|
||||
\"auto_init\": true,
|
||||
\"default_branch\": \"main\"
|
||||
}" \
|
||||
"${FORGEJO_URL}/api/v1/user/repos")
|
||||
URL=$(echo "$RESULT" | jq -r '.html_url // empty')
|
||||
[[ -n "$URL" ]] && info "Test repo created: $URL" || warn "Repo creation response was unexpected — check Forgejo."
|
||||
else
|
||||
info "Test repo already exists."
|
||||
fi
|
||||
|
||||
# ── Clone repos ───────────────────────────────────────────────────────────────
|
||||
step "Cloning repos into $WORK_DIR"
|
||||
mkdir -p "$WORK_DIR"
|
||||
|
||||
# Test repo
|
||||
TEST_DIR="$WORK_DIR/$TEST_REPO"
|
||||
if [[ ! -d "$TEST_DIR/.git" ]]; then
|
||||
info "Cloning test repo..."
|
||||
git clone "${REMOTE_BASE}/${TEST_REPO}.git" "$TEST_DIR"
|
||||
else
|
||||
info "Test repo already cloned."
|
||||
fi
|
||||
|
||||
cd "$TEST_DIR"
|
||||
git config user.email "$GIT_EMAIL"
|
||||
git config user.name "$GIT_NAME"
|
||||
|
||||
# ── Bootstrap README.md from main repo content ───────────────────────────────
|
||||
step "Bootstrapping README.md in test repo"
|
||||
if [[ ! -f "$TEST_DIR/README.md" ]] || ! grep -q 'version:' "$TEST_DIR/README.md" 2>/dev/null; then
|
||||
cat > "$TEST_DIR/README.md" <<'EOF'
|
||||
# KiteStacks Homelab
|
||||
|
||||
<!-- version: 1.3.0 -->
|
||||
|
||||
Private GitOps repository for the KiteStacks homelab.
|
||||
|
||||
## Cluster
|
||||
|
||||
- K3s
|
||||
- FluxCD (planned)
|
||||
- Longhorn (planned)
|
||||
|
||||
## Applications
|
||||
|
||||
- Homepage
|
||||
- Kavita
|
||||
- Linkding
|
||||
- Forgejo
|
||||
- Grafana
|
||||
- Prometheus
|
||||
- Authentik
|
||||
- Open WebUI
|
||||
- LiteLLM
|
||||
|
||||
## Documentation
|
||||
|
||||
docs/KiteStacks-Homelab-Documentation-v1.3.0.md
|
||||
EOF
|
||||
|
||||
mkdir -p "$TEST_DIR/docs"
|
||||
cat > "$TEST_DIR/docs/KiteStacks-Homelab-Documentation-v1.3.0.md" <<'EOF'
|
||||
# KiteStacks Homelab Documentation v1.3.0
|
||||
|
||||
**Version:** 1.3.0
|
||||
**Updated:** Initial autosync bootstrap
|
||||
|
||||
---
|
||||
|
||||
## 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/ |
|
||||
EOF
|
||||
|
||||
cat > "$TEST_DIR/CHANGELOG.md" <<'EOF'
|
||||
# Changelog
|
||||
|
||||
All notable changes to KiteStacks Homelab are documented here.
|
||||
|
||||
## [v1.3.0] — Initial autosync bootstrap
|
||||
|
||||
- Automated sync system installed
|
||||
EOF
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: bootstrap autosync README and docs v1.3.0"
|
||||
git push origin HEAD
|
||||
info "Test repo bootstrapped with v1.3.0 content."
|
||||
else
|
||||
info "README.md already has version tag — skipping bootstrap."
|
||||
fi
|
||||
|
||||
# ── Set initial state to test ─────────────────────────────────────────────────
|
||||
echo "test" > "$STATE_FILE"
|
||||
|
||||
# ── Install systemd service ───────────────────────────────────────────────────
|
||||
step "Installing systemd service"
|
||||
cat > /etc/systemd/system/kitestacks-autosync.service <<EOF
|
||||
[Unit]
|
||||
Description=KiteStacks HomelaB Auto-Sync
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/bash ${SCRIPT_DIR}/scripts/kitestacks-watcher.sh
|
||||
Restart=on-failure
|
||||
RestartSec=15
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=kitestacks-autosync
|
||||
Environment=HOME=/root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable kitestacks-autosync
|
||||
systemctl restart kitestacks-autosync
|
||||
sleep 2
|
||||
|
||||
info "Service installed and started."
|
||||
systemctl status kitestacks-autosync --no-pager || true
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
info "Setup complete!"
|
||||
echo ""
|
||||
info " Test repo : ${FORGEJO_URL}/${FORGEJO_USER}/${TEST_REPO}"
|
||||
info " Watching : $WATCH_DIRS"
|
||||
info " Live logs : journalctl -u kitestacks-autosync -f"
|
||||
info " Status : bash status.sh"
|
||||
echo ""
|
||||
warn "Next step: trigger a test change, then check the test repo."
|
||||
warn "Once verified, run: sudo bash promote.sh"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
85
status.sh
Normal file
85
status.sh
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# status.sh — KiteStacks AutoSync health check
|
||||
# =============================================================================
|
||||
|
||||
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"
|
||||
|
||||
BOLD='\033[1m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
|
||||
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||
fail() { echo -e " ${RED}✗${NC} $1"; }
|
||||
info() { echo -e " ${CYAN}·${NC} $1"; }
|
||||
|
||||
source "$CONFIG_FILE" 2>/dev/null || { echo "Config not found at $CONFIG_FILE"; exit 1; }
|
||||
|
||||
echo -e "\n${BOLD}╔══ KiteStacks AutoSync Status ════════════════════════════════╗${NC}"
|
||||
|
||||
# ── Service ───────────────────────────────────────────────────────────────────
|
||||
echo -e "\n${BOLD}Service${NC}"
|
||||
if systemctl is-active --quiet kitestacks-autosync 2>/dev/null; then
|
||||
ok "kitestacks-autosync is running"
|
||||
else
|
||||
fail "kitestacks-autosync is NOT running"
|
||||
echo " → sudo systemctl start kitestacks-autosync"
|
||||
fi
|
||||
systemctl is-enabled --quiet kitestacks-autosync 2>/dev/null \
|
||||
&& ok "Enabled at boot" || info "Not enabled at boot"
|
||||
|
||||
# ── Active target ─────────────────────────────────────────────────────────────
|
||||
echo -e "\n${BOLD}Active Target${NC}"
|
||||
TARGET="$(cat "$STATE_FILE" 2>/dev/null || echo "test")"
|
||||
if [[ "$TARGET" == "main" ]]; then
|
||||
ok "→ MAIN: ${FORGEJO_URL}/${FORGEJO_USER}/${MAIN_REPO}"
|
||||
else
|
||||
info "→ TEST: ${FORGEJO_URL}/${FORGEJO_USER}/${TEST_REPO}"
|
||||
info " (run 'sudo bash promote.sh' when you're happy with the test repo)"
|
||||
fi
|
||||
|
||||
# ── Workspace ─────────────────────────────────────────────────────────────────
|
||||
echo -e "\n${BOLD}Workspace${NC}"
|
||||
RNAME="$([[ "$TARGET" == "main" ]] && echo "$MAIN_REPO" || echo "$TEST_REPO")"
|
||||
RDIR="$WORK_DIR/$RNAME"
|
||||
|
||||
if [[ -d "$RDIR/.git" ]]; then
|
||||
ok "Cloned: $RDIR"
|
||||
VER=$(grep -oP '(?<=version:\s)[\d.]+' "$RDIR/README.md" 2>/dev/null | head -1 || echo "unknown")
|
||||
info "Current version : $VER"
|
||||
LAST=$(cd "$RDIR" && git log -1 --format="%h %s (%ar)" 2>/dev/null)
|
||||
info "Last commit : $LAST"
|
||||
DOC_COUNT=$(ls "$RDIR/docs/"KiteStacks-Homelab-Documentation-v*.md 2>/dev/null | wc -l)
|
||||
info "Doc versions : $DOC_COUNT file(s) in docs/"
|
||||
else
|
||||
fail "Workspace not found — run setup.sh first"
|
||||
fi
|
||||
|
||||
# ── Forgejo API ───────────────────────────────────────────────────────────────
|
||||
echo -e "\n${BOLD}Forgejo Connectivity${NC}"
|
||||
HTTP=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 \
|
||||
-H "Authorization: token $FORGEJO_TOKEN" \
|
||||
"${FORGEJO_URL}/api/v1/user" 2>/dev/null)
|
||||
[[ "$HTTP" == "200" ]] && ok "API reachable ($FORGEJO_URL)" || fail "API returned HTTP $HTTP"
|
||||
|
||||
# ── Watch dirs ────────────────────────────────────────────────────────────────
|
||||
echo -e "\n${BOLD}Watched Directories${NC}"
|
||||
for d in $WATCH_DIRS; do
|
||||
if [[ -d "$d" ]]; then
|
||||
COUNT=$(find "$d" -type f 2>/dev/null | wc -l)
|
||||
ok "$d ($COUNT files)"
|
||||
else
|
||||
fail "$d (does not exist)"
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Recent logs ───────────────────────────────────────────────────────────────
|
||||
echo -e "\n${BOLD}Recent Log Entries (last 10)${NC}"
|
||||
journalctl -u kitestacks-autosync -n 10 --no-pager -q 2>/dev/null \
|
||||
| sed 's/^/ /' \
|
||||
|| info "No journal entries yet."
|
||||
|
||||
echo -e "\n${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo -e " Live logs: ${CYAN}journalctl -u kitestacks-autosync -f${NC}\n"
|
||||
Reference in a new issue