#!/bin/bash set -euo pipefail IFS=$'\n\t' SRC_ROOT="$HOME/Documents/2026/projects" DST_ROOT="$HOME/projects" SUBDIR="doc" FSTAB="/etc/fstab" die() { echo "Fehler: $*" >&2; exit 1; } 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 } safe_umount() { local target="$1" is_mounted "$target" || return 0 sudo umount "$target" 2>/dev/null || abort_busy "$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 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 "✓ $removed Mount(s) entfernt, Inhalte kopiert, fstab bereinigt." } 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 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