From 5c14ed6168fbffba283c0b3b3a1ce4d9fd235142 Mon Sep 17 00:00:00 2001 From: Marek Date: Sun, 3 May 2026 23:11:56 +0200 Subject: [PATCH] update --- ma-sync | 176 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 132 insertions(+), 44 deletions(-) diff --git a/ma-sync b/ma-sync index 7b28ca1..3ceda75 100755 --- a/ma-sync +++ b/ma-sync @@ -1,58 +1,146 @@ #!/bin/bash -set -e +set -euo pipefail +IFS=$'\n\t' -remove=0 -if [[ "$1" == "--remove" ]]; then - remove=1 -fi +SRC_ROOT="$HOME/Documents/2026/projects" +DST_ROOT="$HOME/projects" +SUBDIR="doc" +FSTAB="/etc/fstab" -source="$(pwd)" -name="$(basename "$source")" -target="$HOME/projects/$name/doc" +die() { echo "Fehler: $*" >&2; exit 1; } -if [[ "$source" == "$HOME" ]]; then - echo "Fehler: \$HOME selbst kann nicht gemountet werden." >&2 +is_mounted() { findmnt -n "$1" >/dev/null 2>&1; } + +bind_source_of() { + awk -v t="$1" '$5 == t {print $4; exit}' /proc/self/mountinfo +} + +abort_busy() { + local target="$1" + echo "Fehler: '$target' ist belegt — kann nicht ausgehängt werden." >&2 + echo "Folgende Prozesse halten das Verzeichnis offen:" >&2 + lsof +D "$target" 2>/dev/null | head -20 >&2 || true exit 1 -fi -if [[ -z "$name" || "$name" == "/" ]]; then - echo "Fehler: ungültiger Ordnername." >&2 - exit 1 -fi -if [[ "$source" == "$target" ]]; then - echo "Fehler: Source und Target sind identisch." >&2 - exit 1 -fi -case "$target/" in "$source"/*) echo "Fehler: Target liegt unter Source (Rekursionsgefahr)." >&2; exit 1;; esac -case "$source/" in "$target"/*) echo "Fehler: Source liegt unter Target (Rekursionsgefahr)." >&2; exit 1;; esac +} -fstab_line="$source $target none bind,nofail 0 0" -fstab_pattern="\\# $target #d" +safe_umount() { + local target="$1" + is_mounted "$target" || return 0 + sudo umount "$target" 2>/dev/null || abort_busy "$target" +} -if (( remove )); then - if mountpoint -q "$target"; then - sudo umount "$target" - cp -a "$source/." "$target/" +remove_fstab_targets() { + (( $# > 0 )) || return 0 + local tmp list + tmp=$(mktemp) + list=$(printf '%s\n' "$@") + awk -v targets="$list" ' + BEGIN { n = split(targets, arr, "\n"); for (i in arr) if (arr[i] != "") skip[arr[i]] = 1 } + !($2 in skip) { print } + ' "$FSTAB" > "$tmp" + sudo install -m 644 -o root -g root "$tmp" "$FSTAB" + rm -f "$tmp" +} + +append_fstab() { + echo "$1 $2 none bind,nofail 0 0" | sudo tee -a "$FSTAB" > /dev/null +} + +fstab_bind_targets_under() { + local prefix="$1" + awk -v p="$prefix/" ' + $3 == "none" && $4 ~ /(^|,)bind(,|$)/ && index($2, p) == 1 { print $1 "\t" $2 } + ' "$FSTAB" +} + +mode_remove() { + local entries=() + mapfile -t entries < <(fstab_bind_targets_under "$DST_ROOT") + if (( ${#entries[@]} == 0 )); then + echo "Keine ma-sync Mounts in fstab gefunden." + return 0 fi - sudo sed -i "$fstab_pattern" /etc/fstab + + local targets=() removed=0 src target entry + for entry in "${entries[@]}"; do + IFS=$'\t' read -r src target <<< "$entry" + echo "→ Entferne: $target (Source: $src)" + safe_umount "$target" + if [[ -d "$src" ]]; then + cp -a "$src/." "$target/" + else + echo " Warnung: Source '$src' existiert nicht mehr — Target bleibt leer." >&2 + fi + targets+=("$target") + removed=$((removed + 1)) + done + + remove_fstab_targets "${targets[@]}" sudo systemctl daemon-reload - echo "Entfernt: $target" - exit 0 -fi + echo "✓ $removed Mount(s) entfernt, Inhalte kopiert, fstab bereinigt." +} -mkdir -p "$target" +mode_mount() { + [[ -d "$SRC_ROOT" ]] || die "Source-Root '$SRC_ROOT' existiert nicht." -if ! mountpoint -q "$target" && [[ -n "$(ls -A "$target" 2>/dev/null)" ]]; then - echo "Target nicht leer — wird durch Source überschrieben: $target" >&2 - find "$target" -mindepth 1 -delete -fi + local mounted=0 skipped=0 remounted=0 -if mountpoint -q "$target"; then - sudo umount "$target" -fi -sudo sed -i "$fstab_pattern" /etc/fstab + shopt -s nullglob + local src name target + for src in "$SRC_ROOT"/*/; do + src="${src%/}" + name=$(basename "$src") + target="$DST_ROOT/$name/$SUBDIR" -sudo mount --bind "$source" "$target" -echo "$fstab_line" | sudo tee -a /etc/fstab > /dev/null -sudo systemctl daemon-reload + # Sicherheitschecks + [[ -z "$name" || "$name" == "/" ]] && continue + [[ "$src" == "$HOME" ]] && continue + [[ "$src" == "$target" ]] && continue + case "$target/" in "$src"/*) continue;; esac + case "$src/" in "$target"/*) continue;; esac -echo "Gespiegelt: $source ↔ $target" + # Nur mounten, wenn das Target-Parent-Verzeichnis (mit gleichem Namen) bereits existiert + if [[ ! -d "$DST_ROOT/$name" ]]; then + echo "↷ kein Target-Container: $DST_ROOT/$name fehlt → übersprungen" + skipped=$((skipped + 1)) + continue + fi + + if is_mounted "$target"; then + local cur + cur=$(bind_source_of "$target") + if [[ "$cur" == "$src" ]]; then + echo "↻ skip (bereits korrekt): $target" + skipped=$((skipped + 1)) + continue + fi + echo "↺ Re-mount: $target (war: $cur, neu: $src)" + safe_umount "$target" + remounted=$((remounted + 1)) + fi + + mkdir -p "$target" + if [[ -n "$(ls -A "$target" 2>/dev/null)" ]]; then + echo " Target '$target' nicht leer — wird geleert." >&2 + find "$target" -mindepth 1 -delete + fi + + remove_fstab_targets "$target" + sudo mount --bind "$src" "$target" + append_fstab "$src" "$target" + echo "✓ gemountet: $src → $target" + mounted=$((mounted + 1)) + done + shopt -u nullglob + + sudo systemctl daemon-reload + + echo "" + echo "Zusammenfassung: $mounted gemountet (davon $remounted Re-mount), $skipped übersprungen." +} + +case "${1:-}" in + --remove) mode_remove ;; + "") mode_mount ;; + *) die "Unbekanntes Argument: '$1' (erwartet: --remove oder leer)" ;; +esac