Skip to content

Sync Repo

Konfiguracja synchronizacji repozytorium

Skrypt synchronizacji repozytorium realizuje:

  • synchronizację definicji stacków Docker Compose (bez danych i sekretów),
  • synchronizację wybranych plików konfiguracyjnych z katalogu /etc,
  • synchronizację konfiguracji systemd z katalogu /etc/systemd,
  • synchronizację własnych jednostek i override’ów systemd z katalogu /etc/systemd/system,
  • blokadę równoległego uruchomienia skryptu (mechanizm lock),
  • walidację konfiguracji i wymaganych zmiennych środowiskowych,
  • korektę uprawnień w repozytorium po zakończeniu synchronizacji.

Plik konfiguracyjny

Tworzymy plik konfiguracyjny dla skryptu synchronizacji:

sudo micro /srv/config/git/sync.env

Treść pliku:

REPO_ROOT="/srv/config/apps-server"
REPO_USER="fulcro"
REPO_GROUP="fulcro"

SOURCE_COMPOSE="/srv/docker/compose"
SOURCE_SCRIPTS_BACKUP="/srv/backups/scripts"
SOURCE_SCRIPTS_CONFIG="/srv/config/scripts"

Zabezpieczamy plik:

sudo chown root:root /srv/config/git/sync.env
sudo chmod 600 /srv/config/git/sync.env

Skrypt synchronizacji repozytorium

Tworzymy jeden zbiorczy skrypt odpowiedzialny za synchronizację:

sudo micro /srv/config/scripts/sync-repo.sh
#!/usr/bin/env bash
set -euo pipefail

# ANSI Colors
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[31m'
NC='\033[0m' # No Color

log()      { echo -e "${GREEN}[sync]${NC} $1"; }
header()   { echo -e "\n${CYAN}=== $1 ===${NC}"; }
warn()     { echo -e "${YELLOW}Warning: $1${NC}"; }
error()    { echo -e "${RED}Error: $1${NC}" >&2; }
critical() { echo -e "${RED}CRITICAL ERROR: $1${NC}" >&2; }

umask 022

# Ensure root execution (sync.env is root:root 600)
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
    critical "Run as root (use sudo)."
    exit 1
fi

# ============================================================
# LOCK MECHANISM (Prevent parallel execution)
# ============================================================
LOCK_FILE="/tmp/sync-repo.lock"
exec 200>"$LOCK_FILE"
flock -n 200 || { error "Script is already running."; exit 1; }

# ============================================================
# CONFIGURATION & VALIDATION
# ============================================================

CONFIG_FILE="/srv/config/git/sync.env"

if [[ ! -f "$CONFIG_FILE" ]]; then
    critical "Config file missing at $CONFIG_FILE"
    exit 1
fi

# Verify config file owner and permissions before loading
if [[ "$(stat -c '%U:%G %a' "$CONFIG_FILE")" != "root:root 600" ]]; then
  critical "Unsafe perms/owner for $CONFIG_FILE (expected root:root 600)"
  exit 1
fi

source "$CONFIG_FILE"

if [[ -z "${REPO_ROOT:-}" ]] || [[ -z "${REPO_USER:-}" ]] || [[ -z "${REPO_GROUP:-}" ]]; then
    critical "REPO_ROOT, REPO_USER or REPO_GROUP variables are not set in sync.env"
    exit 1
fi

if [[ ! -d "$REPO_ROOT" ]]; then
    critical "Target directory $REPO_ROOT does not exist."
    exit 1
fi

if [[ "$REPO_ROOT" == "/" ]]; then
    critical "SAFETY STOP: REPO_ROOT is set to /"
    exit 1
fi

if [[ "$REPO_ROOT" != /srv/* ]]; then
    critical "SAFETY STOP: REPO_ROOT must be under /srv (got: $REPO_ROOT)"
    exit 1
fi

# ============================================================
# HELPER FUNCTIONS
# ============================================================

copy_files() {
    local src_dir="$1"
    local dst_rel_path="$2"
    shift 2
    local files=("$@")
    local full_dst="$REPO_ROOT/$dst_rel_path"

    mkdir -p "$full_dst"

    for f in "${files[@]}"; do
        if [[ -f "$src_dir/$f" ]]; then
            cp -a -- "$src_dir/$f" "$full_dst/$f"
            echo "  + Copied: $dst_rel_path/$f"
        else
            echo -e "  ${YELLOW}! Skipped (missing): $src_dir/$f${NC}"
        fi
    done
}

fix_permissions() {
    header "Fixing Permissions"

    log "Setting ownership to $REPO_USER:$REPO_GROUP in $REPO_ROOT..."
    chown -R "$REPO_USER:$REPO_GROUP" "$REPO_ROOT"
    log "Done."
}

# ============================================================
# SYNC MODULES
# ============================================================

sync_compose() {
    header "Sync: Docker Compose"

    local dst="$REPO_ROOT/compose/"
    mkdir -p "$dst"

    if [[ ! -d "${SOURCE_COMPOSE:-}" ]]; then
        warn "SOURCE_COMPOSE not found or unset. Skipping."
        return
    fi

    rsync -a --delete \
      --exclude ".git/" \
      --exclude "**/.env" \
      --exclude "**/data/" \
      --exclude "**/volumes/" \
      --exclude "**/logs/" \
      --exclude "**/backups/" \
      --exclude "**/*.db" \
      --exclude "**/*.sqlite*" \
      "$SOURCE_COMPOSE/" "$dst"

    log "Rsync completed."
}

# Wrapper: /etc/<dir> -> host/etc/<dir>
etc_copy() {
    local rel_dir="$1"
    shift 1

    local src_dir="/etc"
    local dst_rel="host/etc"

    if [[ "$rel_dir" != "." ]]; then
        src_dir="/etc/$rel_dir"
        dst_rel="host/etc/$rel_dir"
    fi

    copy_files "$src_dir" "$dst_rel" "$@"
}

# Wrapper: /etc/systemd/<dir> -> host/etc/systemd/<dir>
systemd_copy() {
    local rel_dir="$1"
    shift 1

    local src_dir="/etc/systemd"
    local dst_rel="host/etc/systemd"

    if [[ "$rel_dir" != "." ]]; then
        src_dir="/etc/systemd/$rel_dir"
        dst_rel="host/etc/systemd/$rel_dir"
    fi

    copy_files "$src_dir" "$dst_rel" "$@"
}

# Wrapper: /etc/systemd/system/<dir> -> host/etc/systemd/system/<dir>
units_copy() {
    local rel_dir="$1"
    shift 1

    local src_dir="/etc/systemd/system"
    local dst_rel="host/etc/systemd/system"

    if [[ "$rel_dir" != "." ]]; then
        src_dir="/etc/systemd/system/$rel_dir"
        dst_rel="host/etc/systemd/system/$rel_dir"
    fi

    copy_files "$src_dir" "$dst_rel" "$@"
}

sync_etc() {
    header "Sync: Host /etc"

    etc_copy "ssh" sshd_config ssh_config
    etc_copy "fail2ban" jail.local
    etc_copy "ufw" ufw.conf user.rules
    etc_copy "docker" daemon.json
}

sync_systemd() {
    header "Sync: Host /etc/systemd"

    systemd_copy "." journald.conf
}

sync_units() {
    header "Sync: Host /etc/systemd/system"

    units_copy "." restic-backup.service restic-backup.timer
}

sync_host() {
    header "Sync: Host (etc + systemd + units)"
    sync_etc
    sync_systemd
    sync_units
}

# ============================================================
# EXECUTION
# ============================================================

TARGET=${1:-host}

case "$TARGET" in
    compose)  sync_compose;             fix_permissions ;;
    etc)      sync_etc;                 fix_permissions ;;
    systemd)  sync_systemd;             fix_permissions ;;
    units)    sync_units;               fix_permissions ;;
    host)     sync_host;                fix_permissions ;;
    all)      sync_compose; sync_host;  fix_permissions ;;
    *)        error "Usage: $0 {compose|etc|systemd|units|host|all}"; exit 1 ;;
esac

echo ""
log "Completed successfully."

Po zapisaniu nadajemy prawa do uruchamiania:

sudo chmod +x /srv/config/scripts/sync-repo.sh

Uruchamianie synchronizacji

Synchronizację wykonujemy z katalogu skryptów:

cd /srv/config/scripts
sudo ./sync-repo.sh all

Tryby synchronizacji

Skrypt sync-repo.sh obsługuje następujące tryby pracy:

Tryb compose

Synchronizuje wyłącznie definicje Docker Compose (z katalogu źródłowego do repozytorium).

  • kopiowane są pliki konfiguracyjne stacków,
  • pliki .env, dane runtime i wolumeny są pomijane.
sudo ./sync-repo.sh compose

Tryb etc

Synchronizuje wybrane pliki konfiguracyjne systemu z /etc (np. ssh, fail2ban, ufw, docker).

sudo ./sync-repo.sh etc

Tryb systemd

Synchronizuje konfigurację systemd (np. journald.conf).

sudo ./sync-repo.sh systemd

Tryb units

Synchronizuje jednostki systemd (np. *.service, *.timer).

sudo ./sync-repo.sh units

Tryb host

Synchronizuje całą konfigurację hosta:

  • /etc
  • /etc/systemd
  • /etc/systemd/system

Jest to tryb domyślny.

sudo ./sync-repo.sh host

Tryb all

Wykonuje pełną synchronizację:

  • Docker Compose
  • konfigurację hosta (host)
sudo ./sync-repo.sh all