This commit is contained in:
2026-05-03 23:11:56 +02:00
parent ad7f60afc9
commit 5c14ed6168

174
ma-sync
View File

@@ -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
local mounted=0 skipped=0 remounted=0
shopt -s nullglob
local src name target
for src in "$SRC_ROOT"/*/; do
src="${src%/}"
name=$(basename "$src")
target="$DST_ROOT/$name/$SUBDIR"
# Sicherheitschecks
[[ -z "$name" || "$name" == "/" ]] && continue
[[ "$src" == "$HOME" ]] && continue
[[ "$src" == "$target" ]] && continue
case "$target/" in "$src"/*) continue;; esac
case "$src/" in "$target"/*) continue;; esac
# 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
fi
if mountpoint -q "$target"; then
sudo umount "$target"
fi
sudo sed -i "$fstab_pattern" /etc/fstab
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 mount --bind "$source" "$target"
echo "$fstab_line" | sudo tee -a /etc/fstab > /dev/null
sudo systemctl daemon-reload
sudo systemctl daemon-reload
echo "Gespiegelt: $source ↔ $target"
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