324 lines
14 KiB
Bash
Executable File
324 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
# ==============================================================================================
|
|
# ================================= Git Pull & Execute =========================================
|
|
# ==============================================================================================
|
|
# Pulls the latest scripts from the Gitea repository via SSH.
|
|
# Lives at the repo root — sources load_config.sh from the same directory.
|
|
#
|
|
# ── WHAT THIS SCRIPT DOES ─────────────────────────────────────────────────────────────────────
|
|
# 1. Detects which server it's running on via detect_hosts() (MY_ID)
|
|
# 2. Configures sparse checkout to exclude other servers' credential files
|
|
# Each server only pulls its own host*.conf — never sees peer credentials
|
|
# 3. Pulls or clones latest scripts from Gitea
|
|
# 4. Sets executable permissions on all .sh files
|
|
#
|
|
# ── SPARSE CHECKOUT ───────────────────────────────────────────────────────────────────────────
|
|
# Sparse checkout ensures each server only receives its own host conf:
|
|
# HOST1 pulls: master.conf + host1.conf + all scripts
|
|
# HOST1 skips: host2.conf, host3.conf etc.
|
|
# HOST2 pulls: master.conf + host2.conf + all scripts
|
|
# HOST2 skips: host1.conf, host3.conf etc.
|
|
#
|
|
# Adding a new server:
|
|
# Create host3.conf in the repo
|
|
# All existing servers automatically exclude it on next pull
|
|
# New server gets only its own conf ✅
|
|
#
|
|
# ── GITEA LOCATION DETECTION ──────────────────────────────────────────────────────────────────
|
|
# Detects where Gitea is running at runtime — works through fallback:
|
|
# Gitea local → connects via local IP
|
|
# Gitea remote → connects via Tailscale IP
|
|
# Both fail → falls back to GITEA_DOMAIN if configured
|
|
#
|
|
# ── CONFIGURATION (master.conf) ───────────────────────────────────────────────────────────────
|
|
# GITEA_CONTAINER — Docker container name for Gitea
|
|
# GITEA_REPO_PATH — repo path on Gitea (e.g. Varaverk/varaverk.git)
|
|
# GITEA_DOMAIN — public domain fallback (optional)
|
|
# TARGET_DIR — local path to clone/pull into
|
|
# GITEA_SSH_KEY — SSH key for Gitea authentication
|
|
# SSH_PORT — Gitea SSH port (often 221 or 222)
|
|
#
|
|
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
|
|
# git_pull_execute.sh — normal pull
|
|
# git_pull_execute.sh --dry-run — preview without making changes
|
|
# git_pull_execute.sh --log — verbose output
|
|
# git_pull_execute.sh --status — show config and exit
|
|
# ==============================================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Root level script — load_config.sh is in the same directory
|
|
source "$SCRIPT_DIR/load_config.sh"
|
|
|
|
parse_args "$@"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Setup ━━━
|
|
# ==============================================================================================
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
error "Must be run as root"
|
|
exit 1
|
|
fi
|
|
acquire_lock
|
|
|
|
if ! command -v docker &>/dev/null; then
|
|
error "Docker command not found"
|
|
exit 1
|
|
fi
|
|
|
|
# detect_hosts() sets MY_ID — needed for sparse checkout configuration
|
|
detect_hosts
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Locate Gitea ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_CONTAINERS Locate Gitea ━━━"
|
|
|
|
if docker ps --format "{{.Names}}" 2>/dev/null | grep -q "^${GITEA_CONTAINER}$"; then
|
|
# Gitea is running on this server — use local IP
|
|
GITEA_IP=$(hostname -I | awk '{print $1}')
|
|
log "Gitea running locally — connecting via $GITEA_IP"
|
|
else
|
|
# Gitea not running locally — find it on the remote server via Tailscale
|
|
log "Gitea not running locally — checking remote server"
|
|
GITEA_IP=$(tailscale ip -4 "${REMOTE_SERVER_NAME,,}" 2>/dev/null)
|
|
if [[ -n "$GITEA_IP" ]]; then
|
|
echo " Gitea on $REMOTE_SERVER_NAME — connecting via Tailscale $GITEA_IP"
|
|
elif [[ -n "${GITEA_DOMAIN:-}" ]]; then
|
|
warn "Tailscale resolution failed — falling back to $GITEA_DOMAIN"
|
|
GITEA_IP="$GITEA_DOMAIN"
|
|
else
|
|
error "Cannot find Gitea — local: not running, Tailscale: failed, domain: not configured"
|
|
notify "Git pull failed on $(hostname) — cannot locate Gitea container" "Git Sync" "alert"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
REPO_SSH="git@${GITEA_IP}:${GITEA_REPO_PATH}"
|
|
|
|
require_var REPO_SSH
|
|
require_var TARGET_DIR
|
|
require_var GITEA_SSH_KEY
|
|
require_var SSH_PORT
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Status ━━━
|
|
# ==============================================================================================
|
|
if [[ "$SHOW_STATUS" == true ]]; then
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
|
|
echo "$ICON_NET Repo: $REPO_SSH"
|
|
echo "$ICON_GEAR Target: $TARGET_DIR"
|
|
echo "$ICON_GEAR SSH Key: $GITEA_SSH_KEY"
|
|
echo "$ICON_GEAR SSH Port: $SSH_PORT"
|
|
echo "$ICON_HOST My ID: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_HOST Remote ID: $REMOTE_ID ($REMOTE_SERVER_NAME)"
|
|
echo "$ICON_NOTIFY Notify: unRAID=${NOTIFY_UNRAID:-false} Discord=$([[ -n "${MY_DISCORD_WEBHOOK:-}" ]] && echo enabled || echo disabled)"
|
|
echo "$ICON_GEAR Dry Run: $DRY_RUN"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━"
|
|
exit 0
|
|
fi
|
|
|
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Sparse Checkout Configuration ━━━
|
|
# ==============================================================================================
|
|
# Build the list of host*.conf files that belong to OTHER servers.
|
|
# This server pulls everything EXCEPT those files.
|
|
# MY_ID is set by detect_hosts() — e.g. "HOST1"
|
|
|
|
configure_sparse_checkout() {
|
|
local repo_dir="$1"
|
|
|
|
log "Configuring sparse checkout for $MY_ID..."
|
|
|
|
# Enable sparse checkout
|
|
git -C "$repo_dir" config core.sparseCheckout true 2>/dev/null
|
|
|
|
# Build exclusion list — all host*.conf files except MY_ID's
|
|
local sparse_file="$repo_dir/.git/info/sparse-checkout"
|
|
mkdir -p "$(dirname "$sparse_file")"
|
|
|
|
# Start with: pull everything
|
|
echo "/*" > "$sparse_file"
|
|
|
|
# Exclude each other server's conf file
|
|
# Find all host*.conf files present in the repo
|
|
local excluded=0
|
|
for conf_file in "$repo_dir"/host*.conf; do
|
|
[[ -f "$conf_file" ]] || continue
|
|
local conf_name
|
|
conf_name=$(basename "$conf_file")
|
|
|
|
# Determine which HOST ID owns this conf by grepping its hostname var
|
|
# Pattern: HOST1="unRAID-..." or HOST2="unRAID-..."
|
|
local conf_host_id
|
|
conf_host_id=$(grep -m1 -oP '^\s+HOST[0-9]+(?==)' "$conf_file" 2>/dev/null | tr -d ' ')
|
|
|
|
if [[ -z "$conf_host_id" ]]; then
|
|
log "Cannot determine HOST ID for $conf_name — including in pull (safe default)"
|
|
continue
|
|
fi
|
|
|
|
if [[ "$conf_host_id" != "$MY_ID" ]]; then
|
|
echo "!$conf_name" >> "$sparse_file"
|
|
log "Sparse checkout: excluding $conf_name (belongs to $conf_host_id)"
|
|
((excluded++))
|
|
else
|
|
log "Sparse checkout: including $conf_name (belongs to $MY_ID — this server)"
|
|
fi
|
|
done
|
|
|
|
if [[ "$excluded" -gt 0 ]]; then
|
|
echo " Sparse checkout: excluding $excluded peer conf file(s) — credentials protected"
|
|
else
|
|
log "Sparse checkout: no peer conf files to exclude (single server or first run)"
|
|
fi
|
|
}
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Git Sync ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━ $ICON_SYNC Git Sync ━━━"
|
|
echo "$ICON_NET Repo: $REPO_SSH"
|
|
echo "$ICON_GEAR Target: $TARGET_DIR"
|
|
echo ""
|
|
|
|
START=$(date +%s)
|
|
SYNC_SUCCESS=false
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would sync $REPO_SSH → $TARGET_DIR"
|
|
warn "DRY RUN — would configure sparse checkout for $MY_ID"
|
|
warn "DRY RUN — would exclude peer host*.conf files"
|
|
SYNC_SUCCESS=true
|
|
else
|
|
mkdir -p "$TARGET_DIR"
|
|
git config --global --add safe.directory "$TARGET_DIR"
|
|
cd "$TARGET_DIR" || { error "Cannot cd into $TARGET_DIR"; exit 1; }
|
|
|
|
if [[ -d ".git" ]]; then
|
|
# ── Existing repository ──────────────────────────────────────────────
|
|
echo " Existing repository — updating"
|
|
|
|
# Configure sparse checkout BEFORE pull
|
|
# Uses conf files already present from last pull to determine exclusions
|
|
configure_sparse_checkout "$TARGET_DIR"
|
|
|
|
log "git reset --hard"
|
|
git reset --hard
|
|
|
|
log "git clean -fd"
|
|
git clean -fd
|
|
|
|
echo " Pulling latest changes..."
|
|
if GIT_SSH_COMMAND="ssh -i $GITEA_SSH_KEY -p $SSH_PORT" git pull; then
|
|
echo " Git pull successful"
|
|
SYNC_SUCCESS=true
|
|
else
|
|
error "Git pull failed"
|
|
notify "Git pull failed on $(hostname) — check Gitea connectivity" "Git Sync" "alert"
|
|
exit 1
|
|
fi
|
|
|
|
else
|
|
# ── Fresh clone ──────────────────────────────────────────────────────
|
|
echo " No repository found — cloning"
|
|
|
|
# Clone first — need the repo to exist before configuring sparse checkout
|
|
if GIT_SSH_COMMAND="ssh -i $GITEA_SSH_KEY -p $SSH_PORT" git clone "$REPO_SSH" .; then
|
|
echo " Clone successful"
|
|
|
|
# Configure sparse checkout after clone
|
|
# Now all host*.conf files are present — can detect exclusions
|
|
configure_sparse_checkout "$TARGET_DIR"
|
|
|
|
# Apply sparse checkout — removes excluded files from working tree
|
|
echo " Applying sparse checkout..."
|
|
git read-tree -mu HEAD
|
|
echo " Sparse checkout applied — peer credentials removed from working tree"
|
|
|
|
SYNC_SUCCESS=true
|
|
else
|
|
error "Clone failed"
|
|
notify "Git clone failed on $(hostname) — check Gitea connectivity" "Git Sync" "alert"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# ── Permissions ──────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "━━━ $ICON_GEAR Permissions ━━━"
|
|
log "Setting executable permissions on all .sh files..."
|
|
find "$TARGET_DIR" -type f -name "*.sh" -exec chmod +x {} \;
|
|
echo " Permissions set on .sh files"
|
|
fi
|
|
|
|
END=$(date +%s)
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Conf Upgrade ━━━
|
|
# ==============================================================================================
|
|
# Merges new conf structure into the live conf files after every pull.
|
|
# New keys → added with template defaults (user fills in once).
|
|
# Removed keys → dropped. Existing values → always preserved.
|
|
# Silent when already up to date — no overhead on unchanged pulls.
|
|
|
|
echo ""
|
|
echo "━━━ $ICON_GEAR Conf Upgrade ━━━"
|
|
|
|
UPGRADE_SCRIPT="$TARGET_DIR/Deployment/conf_upgrade.sh"
|
|
TMPL_DIR="$TARGET_DIR/Deployment/conf_templates"
|
|
CONF_DIR="$TARGET_DIR/Configurations"
|
|
|
|
if [[ ! -f "$UPGRADE_SCRIPT" ]]; then
|
|
log "conf_upgrade.sh not found — skipping (pre-deployment-folder repo)"
|
|
elif [[ "$DRY_RUN" == true ]]; then
|
|
warn "DRY RUN — would upgrade master.conf and ${MY_ID,,}.conf"
|
|
elif [[ "$SYNC_SUCCESS" == true ]]; then
|
|
_DRY=""
|
|
|
|
# master.conf
|
|
if [[ -f "$TMPL_DIR/master.conf" && -f "$CONF_DIR/master.conf" ]]; then
|
|
bash "$UPGRADE_SCRIPT" \
|
|
--template "$TMPL_DIR/master.conf" \
|
|
--target "$CONF_DIR/master.conf" \
|
|
--backup $_DRY
|
|
else
|
|
warn "master.conf template or target not found — skipping"
|
|
fi
|
|
|
|
# This server's host conf only — sparse checkout ensures we have it
|
|
HOST_CONF="$CONF_DIR/${MY_ID,,}.conf"
|
|
if [[ -f "$TMPL_DIR/host.conf.template" && -f "$HOST_CONF" ]]; then
|
|
bash "$UPGRADE_SCRIPT" \
|
|
--template "$TMPL_DIR/host.conf.template" \
|
|
--target "$HOST_CONF" \
|
|
--backup $_DRY
|
|
else
|
|
warn "${MY_ID,,}.conf or host.conf.template not found — skipping"
|
|
fi
|
|
fi
|
|
|
|
# ==============================================================================================
|
|
# ━━━ Summary ━━━
|
|
# ==============================================================================================
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY GIT SYNC SUMMARY ━━━━━"
|
|
echo "$ICON_NET Repo: $REPO_SSH"
|
|
echo "$ICON_GEAR Target: $TARGET_DIR"
|
|
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
|
|
echo "$ICON_LOCK Excluded: peer host*.conf files"
|
|
echo "$ICON_TIME Duration: $(format_duration $((END - START)))"
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo "$ICON_WARN Status: DRY RUN — no changes made"
|
|
elif [[ "$SYNC_SUCCESS" == true ]]; then
|
|
echo "$ICON_DONE Status: $ICON_SUCCESS DONE"
|
|
notify "Repository synced successfully on $(hostname)" "Git Sync" "normal"
|
|
else
|
|
echo "$ICON_ERROR Status: $ICON_ERROR FAILED"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" |