Add autosync scripts

This commit is contained in:
Kenpat7177 2026-06-06 04:20:01 -05:00
parent 6aed620678
commit d5b00d9c79
5 changed files with 900 additions and 18 deletions

168
README.md
View file

@ -1,25 +1,157 @@
# KiteStacks Homelab ╔══════════════════════════════════════════════════════════════════╗
║ KiteStacks Homelab — Auto-Sync Setup Guide ║
╚══════════════════════════════════════════════════════════════════╝
Private GitOps repository for the KiteStacks homelab. WHAT THIS DOES
──────────────
Watches your Docker app directories for any file changes.
On each change it automatically:
## Cluster 1. Copies the changed file into the correct repo folder
~/docker/homepage/ → apps/homepage/
~/docker/<app>/ → apps/<app>/
/etc/kitestacks/ → clusters/assassin/
- K3s 2. Bumps the version in README.md
- FluxCD (planned) <!-- version: 1.3.2 --><!-- version: 1.3.3 -->
- Longhorn (planned)
## Applications 3. Creates a new versioned doc file
docs/KiteStacks-Homelab-Documentation-v1.3.3.md
- Homepage 4. Appends an entry to CHANGELOG.md
- Kavita
- Linkding
- Forgejo
- Grafana
- Prometheus
- Authentik
- Open WebUI
- LiteLLM
## Documentation 5. Commits with your existing style:
"Automated update: 2026-06-06 03:36:00"
docs/KiteStacks-Homelab-Documentation-v1.2.md 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.
...
This tag is injected automatically on first run (it won't
change how your README looks in the browser).
The docs/ reference line is also kept current:
docs/KiteStacks-Homelab-Documentation-v1.3.4.md
DIRECTORY MAPPING
═════════════════
When a file changes, it's placed in the repo like this:
Server path Repo path
───────────────────────────────── ─────────────────────────────────
~/docker/homepage/config/… apps/homepage/config/…
~/docker/kavita-docker-automation/… apps/kavita-docker-automation/…
/etc/kitestacks/… clusters/assassin/…
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
View 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}"

View 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
View 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
View 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"