#!/usr/bin/env bash # # mount-nas.sh — připojení/odpojení NAS SMB share do # # Tiché (nobrowse), vhodné pro ruční i automatizované použití. # # Konfigurace přes env proměnné (mají defaulty): # SMB_SERVER default # SMB_SHARE default Multimedia # SMB_USER default mates # TARGET default # KEYCHAIN_SERVICE default SMB:/Multimedia # NET_CHECK_TIMEOUT default 5 (sekund) # # Akce (default = auto): # --auto mount pokud není, skončí úspěšně pokud je již připojen # --mount mount (chyba pokud již připojen) # --umount umount (chyba pokud není připojen) # --status vypíše aktuální stav, exit 0/1 podle toho zda je mount aktivní # --help nápověda # # Exit kódy: # 0 OK (mount proběhl nebo již připojen / umount proběhl) # 10 cílový mount bod (nadřazený adresář) neexistuje # 11 záznam v Klíčence chybí # 12 záznam v Klíčence je prázdný # 20 SMB server nedostupný (TCP 445) # 30 mount selhal (auth, mount error, ...) # 31 umount selhal (resource busy, ...) # set -uo pipefail # ===== Konfigurace ===== SMB_SERVER="${SMB_SERVER:-}" SMB_SHARE="${SMB_SHARE:-Multimedia}" SMB_USER="${SMB_USER:-mates}" TARGET="${TARGET:-}" KEYCHAIN_SERVICE="${KEYCHAIN_SERVICE:-SMB:$SMB_SERVER/$SMB_SHARE}" NET_CHECK_TIMEOUT="${NET_CHECK_TIMEOUT:-5}" LOG_FILE="${LOG_FILE:-/tmp/mount-nas.log}" PASSWORD_FILE="${PASSWORD_FILE:-}" # ===== Barvy (jen pokud terminál) ===== if [ -t 1 ]; then RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' else RED=''; GREEN=''; YELLOW=''; NC='' fi # ===== Pomocné funkce ===== log() { printf '%s[%s]%s %s\n' "$GREEN" "$(date '+%Y-%m-%d %H:%M:%S')" "$NC" "$*" | tee -a "$LOG_FILE"; } warn() { printf '%s[%s]%s %s\n' "$YELLOW" "$(date '+%Y-%m-%d %H:%M:%S')" "$NC" "$*" | tee -a "$LOG_FILE"; } err() { printf '%s[%s]%s %s\n' "$RED" "$(date '+%Y-%m-%d %H:%M:%S')" "$NC" "$*" | tee -a "$LOG_FILE"; } die() { err "$@"; exit "$1"; } usage() { sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//' } # ===== Parsování argumentů ===== # Podpora: --password-file CESTA (s mezerou) # --password-file=CESTA (s rovnítkem) # Argumenty s hodnotou je nutné ručně posunout v poli, protože 'for arg in "$@"' # neumožní 'shift 2' uvnitř smyčky bez komplikací. Přepíšeme do další proměnné. ACTION="auto" PASSWORD_FILE_ARG="" ARGS=() while [ "$#" -gt 0 ]; do arg="$1" case "$arg" in --help|-h) usage; exit 0 ;; --auto) ACTION="auto" ;; --mount) ACTION="mount" ;; --umount) ACTION="umount" ;; --status) ACTION="status" ;; --password-file) PASSWORD_FILE_ARG="$2"; shift 2; continue ;; --password-file=*) PASSWORD_FILE_ARG="${arg#--password-file=}"; shift ;; *) err "Unknown arg: $arg"; usage; exit 1 ;; esac ARGS+=("$arg") shift done # ===== Detekce aktuálního stavu ===== is_mounted() { mount 2>/dev/null | grep -qE "//$SMB_USER@$SMB_SERVER/$SMB_SHARE on $TARGET \(smbfs," } # ===== 1. Kontrola mount root ===== MOUNT_ROOT="$(dirname "$TARGET")" if [ ! -d "$MOUNT_ROOT" ]; then die 10 "mount root neexistuje: $MOUNT_ROOT (čeká se na SSD)" fi # ===== Akce: status ===== if [ "$ACTION" = "status" ]; then if is_mounted; then log "OK: $TARGET je připojen ($SMB_SERVER/$SMB_SHARE)" mount | grep -E "$TARGET" exit 0 else warn "NEpřipojen: $TARGET" exit 1 fi fi # ===== Akce: umount ===== if [ "$ACTION" = "umount" ]; then if ! is_mounted; then die 1 "$TARGET není připojen" fi log "Umount $TARGET" if /sbin/umount "$TARGET" 2>>"$LOG_FILE"; then log "OK: umount úspěšný" exit 0 else die 31 "umount selhal (resource busy? zavři aplikace/kontejnery)" fi fi # ===== Akce: mount nebo auto ===== if [ "$ACTION" = "auto" ] && is_mounted; then log "OK: $TARGET je již připojen" exit 0 fi if [ "$ACTION" = "mount" ] && is_mounted; then die 30 "$TARGET je již připojen (použij --auto nebo --umount)" fi # ===== 2. Test dostupnosti SMB serveru ===== log "Test dostupnosti SMB serveru $SMB_SERVER:445 (timeout ${NET_CHECK_TIMEOUT}s)" if ! /usr/bin/nc -z -G "$NET_CHECK_TIMEOUT" "$SMB_SERVER" 445 2>/dev/null; then die 20 "SMB server $SMB_SERVER nedostupný (TCP 445)" fi log "OK: SMB server odpovídá" # ===== 3. Heslo z Klíčenky nebo ze souboru ===== # Priorita: # 1. --password-file (heslo v souboru s chmod 600) # 2. $PASSWORD_FILE env proměnná # 3. Klíčenka macOS (service = KEYCHAIN_SERVICE) PASSWORD="" if [ -n "${PASSWORD_FILE_ARG:-}" ] && [ -n "$PASSWORD_FILE_ARG" ]; then if [ ! -f "$PASSWORD_FILE_ARG" ]; then die 11 "soubor s heslem neexistuje: $PASSWORD_FILE_ARG" fi if [ ! -r "$PASSWORD_FILE_ARG" ]; then die 11 "soubor s heslem nelze číst (oprávnění): $PASSWORD_FILE_ARG" fi PASSWORD=$(cat "$PASSWORD_FILE_ARG") PASSWORD="${PASSWORD%%$'\n'}" # odřízne trailing newline if [ -z "$PASSWORD" ]; then die 12 "soubor s heslem je prázdný: $PASSWORD_FILE_ARG" fi log "Heslo načteno ze souboru: $PASSWORD_FILE_ARG" elif [ -n "$PASSWORD_FILE" ] && [ -f "$PASSWORD_FILE" ]; then PASSWORD=$(cat "$PASSWORD_FILE") PASSWORD="${PASSWORD%%$'\n'}" log "Heslo načteno z PASSWORD_FILE: $PASSWORD_FILE" else if ! PASSWORD=$(/usr/bin/security find-generic-password -a "$SMB_USER" -s "$KEYCHAIN_SERVICE" -w 2>/dev/null); then die 11 "Klíčenka chybí ($KEYCHAIN_SERVICE)" fi fi if [ -z "$PASSWORD" ]; then die 12 "Heslo je prázdné" fi # ===== 4. Vlastní mount přes mount_smbfs s heslem v URL ===== # # mount_smbfs podporuje syntaxi: # //user:password@server/share /mount/point # To znamená, že heslo se předá přímo, bez expect a bez interaktivního promptu. # (Bezpečnost: heslo je vidět v 'ps' po dobu trvání mountu, což jsou sekundy.) log "Spouštím mount_smbfs //$SMB_USER@$SMB_SERVER/$SMB_SHARE -> $TARGET" # Sestav URL s heslem. Pro hesla se speciálními znaky (%, @, :) musíme # escapovat, ale heslo z klíčenky většinou tyto znaky nemá. # Pro jistotu zakódujeme heslo pomocí printf %q a pak odstraníme quotes. # mount_smbfs -N zakáže prompt. Pokud heslo v URL je neplatné, mount selže. # Nekombinujeme -N s heslem v URL — buď heslo v URL, nebo -N bez hesla. ESCAPED_URL="//$SMB_USER:$PASSWORD@$SMB_SERVER/$SMB_SHARE" # Pro mount_smbfs chceme tichý režim (-s), ale chyby potřebujeme v logu. if /sbin/mount_smbfs -o nobrowse,nosuid "$ESCAPED_URL" "$TARGET" 2>>"$LOG_FILE"; then log "OK: mount_smbfs exit 0" else rc=$? # Heslo v URL může selhat s různými chybami: # 1 = chyba argumentu (heslo obsahuje nepovolené znaky) # 64 = mount_smbfs obecná chyba (auth, share neexistuje, ...) die 30 "mount_smbfs selhal (exit $rc)" fi # Bezpečnost: ihned smaž heslo z paměti unset PASSWORD unset ESCAPED_URL # ===== 5. Ověření, že mount opravdu proběhl ===== if ! is_mounted; then die 30 "mount exit 0 ale SMB share není v /sbin/mount" fi log "OK: mount úspěšný ($SMB_SERVER/$SMB_SHARE -> $TARGET)"