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

168
ma-sync
View File

@@ -1,58 +1,146 @@
#!/bin/bash #!/bin/bash
set -e set -euo pipefail
IFS=$'\n\t'
remove=0 SRC_ROOT="$HOME/Documents/2026/projects"
if [[ "$1" == "--remove" ]]; then DST_ROOT="$HOME/projects"
remove=1 SUBDIR="doc"
fi FSTAB="/etc/fstab"
source="$(pwd)" die() { echo "Fehler: $*" >&2; exit 1; }
name="$(basename "$source")"
target="$HOME/projects/$name/doc"
if [[ "$source" == "$HOME" ]]; then is_mounted() { findmnt -n "$1" >/dev/null 2>&1; }
echo "Fehler: \$HOME selbst kann nicht gemountet werden." >&2
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 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" safe_umount() {
fstab_pattern="\\# $target #d" local target="$1"
is_mounted "$target" || return 0
sudo umount "$target" 2>/dev/null || abort_busy "$target"
}
if (( remove )); then remove_fstab_targets() {
if mountpoint -q "$target"; then (( $# > 0 )) || return 0
sudo umount "$target" local tmp list
cp -a "$source/." "$target/" 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 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 sudo systemctl daemon-reload
echo "Entfernt: $target" echo "✓ $removed Mount(s) entfernt, Inhalte kopiert, fstab bereinigt."
exit 0 }
mode_mount() {
[[ -d "$SRC_ROOT" ]] || die "Source-Root '$SRC_ROOT' existiert nicht."
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 fi
mkdir -p "$target" mkdir -p "$target"
if [[ -n "$(ls -A "$target" 2>/dev/null)" ]]; then
if ! mountpoint -q "$target" && [[ -n "$(ls -A "$target" 2>/dev/null)" ]]; then echo " Target '$target' nicht leer — wird geleert." >&2
echo "Target nicht leer — wird durch Source überschrieben: $target" >&2
find "$target" -mindepth 1 -delete find "$target" -mindepth 1 -delete
fi fi
if mountpoint -q "$target"; then remove_fstab_targets "$target"
sudo umount "$target" sudo mount --bind "$src" "$target"
fi append_fstab "$src" "$target"
sudo sed -i "$fstab_pattern" /etc/fstab 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