Reported live: mount error(79) again despite already switching to real credentials + sec=ntlmssp — this time with a Samba password containing special characters. Root cause confirmed directly: `read -r -s pw1` without `IFS=` silently strips leading/trailing whitespace even when reading into a single variable (verified: " P@ss word! " -> "P@ss word!", 10 chars instead of 12). A password with a leading/trailing space — common from a password manager's copy-paste, or a stray keystroke — got quietly trimmed on the way into the credentials file, so it no longer matched what was actually set on the Samba account. That mismatch surfaces as this same cryptic ENOKEY mount error, not an obvious "wrong password". Fixed with IFS= on both reads. Also echo the captured length (never the password itself) right after entry, so a silently-stripped character is something you can catch and cross-check yourself before the mount even attempts, instead of only after it fails.
549 lines
24 KiB
Bash
549 lines
24 KiB
Bash
#!/bin/bash
|
|
# services/vpn-data-mount.sh — mount existing SMB shares from a
|
|
# NetBird-connected home box, with SSH-key bootstrap automated.
|
|
# Part of the modular post-install system (sourced by setup.sh).
|
|
#
|
|
# Can also be run standalone on any machine:
|
|
# sudo bash vpn-data-mount.sh
|
|
# (No Docker needed — this only touches SSH and /etc/fstab)
|
|
#
|
|
# Unlike most services here, this is repeatable by design: different
|
|
# services can have data on different home boxes, so this asks for a home
|
|
# box IP every time and can be re-run any number of times, once per
|
|
# home-box you want to pull shares from. It's the multi-instance pattern
|
|
# from CLAUDE.md generalized from "N instances of one app" to "N
|
|
# independent mounts" — there's no single install directory to gate on, so
|
|
# state lives in /etc/fstab itself (tagged entries), same as
|
|
# tools/mount-network-drive.sh.
|
|
#
|
|
# Deliberately READ-ONLY on the home box's Samba config — this tool never
|
|
# writes to smb.conf, never installs Samba, never creates or resets a
|
|
# Samba account there. It only (a) bootstraps passwordless SSH if needed,
|
|
# then (b) reads the home box's existing smb.conf over that SSH connection
|
|
# to list whatever shares are already configured there, so you can pick
|
|
# one or more to mount. Set up the actual share(s) on the home box
|
|
# yourself, the normal way (or with tools/mount-network-drive.sh's own
|
|
# guided flow, run there). An earlier version of this tried to fully
|
|
# provision Samba remotely too — reversed per direct request, and it had
|
|
# also caused real damage in practice (a section-removal bug that deleted
|
|
# unrelated shares on a real box) that a read-only tool can't repeat.
|
|
#
|
|
# Assumes the home box is Linux and reachable over a NetBird IP — this repo
|
|
# doesn't set up the home box's side of NetBird (that's a separate machine,
|
|
# possibly not running this repo at all).
|
|
#
|
|
# SMB chosen over NFS/SSHFS deliberately: NFS is marginally faster for
|
|
# Linux-to-Linux but SMB isn't a "huge" difference for normal use (media,
|
|
# docs, moderate datasets — the gap shows up mainly on many-small-files
|
|
# workloads). SSHFS was ruled out because the VPN tunnel already encrypts
|
|
# everything — SSHFS's own SSH-layer encryption on top of that is pure
|
|
# redundant overhead for no added security, and it's the slowest and least
|
|
# robust (FUSE reconnect quirks) of the three for an always-on mount.
|
|
#
|
|
# Mounts use real Samba credentials (a username/password you provide for
|
|
# an account that already exists on the home box), stored locally in a
|
|
# root-only credentials file, same convention tools/mount-network-drive.sh
|
|
# already uses — never guest access. A CIFS guest mount with no explicit
|
|
# security mode can hit "mount error(79): Can not access a needed shared
|
|
# library" — a misleadingly-worded cifs-utils message for errno 79
|
|
# (ENOKEY), a known rough edge in the kernel cifs.ko keyring/upcall path
|
|
# for anonymous sessions. Credentialed mounts with an explicit sec=ntlmssp
|
|
# take the normal NTLMSSP auth path instead and don't hit it.
|
|
|
|
# ── Standalone bootstrap ──────────────────────────────────────────────────────
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
[[ "$(id -u)" == "0" ]] || { echo "Run with sudo: sudo bash $0"; exit 1; }
|
|
|
|
_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
_COMMON="$_SELF_DIR/../lib/common.sh"
|
|
|
|
if [[ -f "$_COMMON" ]]; then
|
|
# shellcheck source=../lib/common.sh
|
|
source "$_COMMON"
|
|
else
|
|
log_info() { echo -e "\033[0;34m[INFO]\033[0m $*"; }
|
|
log_success() { echo -e "\033[0;32m[OK]\033[0m $*"; }
|
|
log_warning() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
|
|
log_error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }
|
|
|
|
prompt_text() {
|
|
local _q="$1" _def="$2" _var="$3" _r
|
|
[[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='$_def'"; return; }
|
|
read -r -p " $_q " _r
|
|
eval "$_var='${_r:-$_def}'"
|
|
}
|
|
|
|
prompt_yn() {
|
|
local _q="$1" _def="$2" _var="$3" _r
|
|
[[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='$_def'"; return; }
|
|
read -r -p " $_q " _r
|
|
eval "$_var='${_r:-$_def}'"
|
|
}
|
|
|
|
register_service() { :; }
|
|
fi
|
|
|
|
ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
|
|
ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" 2>/dev/null | cut -d: -f6 || echo "${HOME:-/root}")"
|
|
DRY_RUN="${DRY_RUN:-false}"
|
|
UNATTENDED="${UNATTENDED:-false}"
|
|
|
|
_RUN_STANDALONE=1
|
|
fi
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
register_service vpn-data-mount homelab "Mount existing SMB shares from a NetBird-connected home box (read-only discovery, SSH-automated key setup)"
|
|
|
|
# ── fstab tagging — the durable record of what this tool has set up ────────
|
|
# Same philosophy as tools/mount-network-drive.sh: /etc/fstab is the single
|
|
# source of truth, no separate state file to drift out of sync with it.
|
|
_VDM_TAG_PREFIX="# vpn-data-mount:"
|
|
|
|
_vdm_list_existing() {
|
|
local entries
|
|
entries="$(grep "^${_VDM_TAG_PREFIX}" /etc/fstab 2>/dev/null || true)"
|
|
if [ -n "$entries" ]; then
|
|
echo ""
|
|
log_info "Already-configured VPN data mounts:"
|
|
echo "$entries" | sed "s|^${_VDM_TAG_PREFIX}| •|"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# ── Name a raw IP so it can be used everywhere instead of typing it again ──
|
|
# Deliberately /etc/hosts, not ~/.ssh/config: an SSH Host alias only helps
|
|
# the `ssh` command itself resolve a name — mount.cifs (and everything
|
|
# else) never consults ~/.ssh/config at all, so an alias alone wouldn't let
|
|
# the actual CIFS mount address use a name. /etc/hosts is the one mechanism
|
|
# that makes a name resolve for both, which is what "use a name instead of
|
|
# the IP" actually needs end to end.
|
|
# Sets RESOLVED_HOST (not local — same out-param convention as elsewhere).
|
|
_vdm_resolve_host() {
|
|
local input="$1"
|
|
RESOLVED_HOST="$input"
|
|
|
|
# Not a raw IP (already a name, whether from /etc/hosts, real DNS, or
|
|
# just typed that way) — nothing to do.
|
|
[[ "$input" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 0
|
|
|
|
# Already named from an earlier vpn-data-mount run against this same IP?
|
|
local existing
|
|
existing="$(awk -v ip="$input" '$1==ip && /# vpn-data-mount/ {print $2; exit}' /etc/hosts 2>/dev/null)"
|
|
if [ -n "$existing" ]; then
|
|
log_info "Already named '$existing' in /etc/hosts from an earlier mount — using that."
|
|
RESOLVED_HOST="$existing"
|
|
return 0
|
|
fi
|
|
|
|
local NAME=""
|
|
prompt_text " Name this home box (blank to keep using the IP):" "" NAME
|
|
[ -z "$NAME" ] && return 0
|
|
NAME="$(echo "$NAME" | tr -cs 'a-zA-Z0-9-' '-' | sed 's/^-*//;s/-*$//')"
|
|
[ -z "$NAME" ] && return 0
|
|
|
|
if grep -qE "^\S+[[:space:]]+${NAME}([[:space:]]|\$)" /etc/hosts 2>/dev/null; then
|
|
log_warning "'$NAME' is already used for a different address in /etc/hosts — keeping the IP instead."
|
|
return 0
|
|
fi
|
|
|
|
echo "${input} ${NAME} # vpn-data-mount" >> /etc/hosts
|
|
log_success "Added to /etc/hosts: $NAME -> $input (works for SSH, this mount, and anything else on this box)"
|
|
RESOLVED_HOST="$NAME"
|
|
}
|
|
|
|
# ── SSH trust: test first, only bootstrap if actually needed ──────────────
|
|
# Covers "the home box and VPS already share a key via GitHub import (or any
|
|
# other means)" for free — if it already works, nothing below runs at all.
|
|
_vdm_ssh_works() {
|
|
local user="$1" host="$2"
|
|
# Runs as $ACTUAL_USER, not root (this whole script runs as root) — the
|
|
# SSH key lives in $ACTUAL_HOME/.ssh, so root's own bare `ssh` would look
|
|
# in the wrong home directory entirely and never find it.
|
|
sudo -u "$ACTUAL_USER" ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \
|
|
"${user}@${host}" true 2>/dev/null
|
|
}
|
|
|
|
_vdm_ensure_ssh_trust() {
|
|
local user="$1" host="$2"
|
|
|
|
if _vdm_ssh_works "$user" "$host"; then
|
|
log_success "Passwordless SSH to ${user}@${host} already works — nothing to set up."
|
|
return 0
|
|
fi
|
|
|
|
log_info "No passwordless SSH to ${user}@${host} yet — setting it up."
|
|
|
|
local keyfile="$ACTUAL_HOME/.ssh/id_ed25519"
|
|
if [ ! -f "$keyfile" ]; then
|
|
log_info "No SSH key found at $keyfile — generating one."
|
|
sudo -u "$ACTUAL_USER" mkdir -p "$ACTUAL_HOME/.ssh"
|
|
sudo -u "$ACTUAL_USER" ssh-keygen -t ed25519 -N "" -f "$keyfile" -C "${ACTUAL_USER}@$(hostname)-vpn-data-mount" \
|
|
|| { log_error "Key generation failed."; return 1; }
|
|
chmod 700 "$ACTUAL_HOME/.ssh"
|
|
chmod 600 "$keyfile"
|
|
chmod 644 "${keyfile}.pub"
|
|
fi
|
|
|
|
echo ""
|
|
echo " This box's public key (needs to end up in ${user}'s authorized_keys"
|
|
echo " on the home box, one way or another):"
|
|
echo ""
|
|
sed 's/^/ /' "${keyfile}.pub"
|
|
echo ""
|
|
|
|
while true; do
|
|
echo " How do you want to get it there?"
|
|
echo " 1) Try now with ssh-copy-id (needs password login enabled on the home box)"
|
|
echo " 2) I'll add it myself — paste it into ~/.ssh/authorized_keys there, or add it"
|
|
echo " to your GitHub account and run 'ssh-import-id gh:<user>' on the home box"
|
|
echo " (same mechanism this repo's own base.sh setup uses)"
|
|
echo " 3) Cancel this mount"
|
|
echo ""
|
|
local CHOICE=""
|
|
prompt_text " Choice [1/2/3]:" "1" CHOICE
|
|
case "$CHOICE" in
|
|
1)
|
|
sudo -u "$ACTUAL_USER" ssh-copy-id -i "${keyfile}.pub" "${user}@${host}" \
|
|
|| log_warning "ssh-copy-id failed — password auth may be disabled on the home box. Try option 2."
|
|
;;
|
|
2)
|
|
echo ""
|
|
read -r -p " Press Enter once the key is in place on the home box: " _
|
|
;;
|
|
3|c|C)
|
|
log_info "Cancelled."
|
|
return 1
|
|
;;
|
|
*)
|
|
log_warning "Invalid choice."
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
if _vdm_ssh_works "$user" "$host"; then
|
|
log_success "Passwordless SSH to ${user}@${host} confirmed."
|
|
return 0
|
|
fi
|
|
log_warning "Still can't connect without a password — try again, or cancel."
|
|
done
|
|
}
|
|
|
|
# ── Read-only share discovery ───────────────────────────────────────────────
|
|
# Prints "share_name|path" one per line for every real data share found in
|
|
# the home box's smb.conf (skips [global]/[homes]/[printers]/[print$] —
|
|
# not actual browsable directories). Never writes anything, on either
|
|
# side — see the file header. Tries a plain read first (smb.conf is
|
|
# world-readable on a stock Samba install); falls back to a sudo'd read
|
|
# only if that comes back empty, still read-only either way.
|
|
_vdm_list_remote_shares() {
|
|
local user="$1" host="$2"
|
|
local conf
|
|
conf="$(sudo -u "$ACTUAL_USER" ssh "${user}@${host}" 'cat /etc/samba/smb.conf 2>/dev/null')"
|
|
if [ -z "$conf" ]; then
|
|
conf="$(sudo -u "$ACTUAL_USER" ssh -t "${user}@${host}" 'sudo cat /etc/samba/smb.conf 2>/dev/null' 2>/dev/null)"
|
|
fi
|
|
[ -z "$conf" ] && return 1
|
|
|
|
echo "$conf" | awk '
|
|
function flush() {
|
|
if (sect != "" && path != "" && sect != "global" && sect != "printers" && sect != "print$" && sect != "homes") {
|
|
print sect "|" path
|
|
}
|
|
}
|
|
/^\[/ {
|
|
flush()
|
|
sect = $0
|
|
gsub(/[][]/, "", sect)
|
|
path = ""
|
|
next
|
|
}
|
|
/^[[:space:]]*path[[:space:]]*=/ {
|
|
path = $0
|
|
sub(/^[[:space:]]*path[[:space:]]*=[[:space:]]*/, "", path)
|
|
gsub(/[[:space:]]+$/, "", path)
|
|
}
|
|
END { flush() }
|
|
'
|
|
}
|
|
|
|
# ── Reuse a password already entered for the same user+host ───────────────
|
|
# Prints the password if an earlier mount from this host used the same
|
|
# Samba username, nothing otherwise. No log_* calls in here — this runs
|
|
# inside a caller's $(...) capture, and log_info/log_warning/etc. all
|
|
# write to stdout, which would corrupt it.
|
|
_vdm_find_existing_smb_password() {
|
|
local host="$1" user="$2" label creds_file found_user found_pass
|
|
while IFS= read -r label; do
|
|
[ -z "$label" ] && continue
|
|
creds_file="/etc/samba/credentials.vpn-data-mount-${label}"
|
|
[ -f "$creds_file" ] || continue
|
|
found_user="$(grep '^username=' "$creds_file" | cut -d= -f2-)"
|
|
[ "$found_user" = "$user" ] || continue
|
|
found_pass="$(grep '^password=' "$creds_file" | cut -d= -f2-)"
|
|
[ -n "$found_pass" ] && { printf '%s' "$found_pass"; return 0; }
|
|
done < <(grep -E "^${_VDM_TAG_PREFIX} [^ ]+ — ${host}:" /etc/fstab 2>/dev/null \
|
|
| sed -E "s/^${_VDM_TAG_PREFIX} ([^ ]+) .*/\1/")
|
|
return 1
|
|
}
|
|
|
|
# ── Prompt for a password twice, hidden, matching ──────────────────────────
|
|
# Prints the password on success. No log_* calls — same reason as above;
|
|
# uses plain stderr output instead so it's visible without corrupting a
|
|
# caller's $(...) capture.
|
|
_vdm_prompt_password() {
|
|
local prompt="$1" pw1="" pw2=""
|
|
while true; do
|
|
# IFS= matters here, not just -s/-r: plain `read -r pw1` (no IFS=)
|
|
# silently strips leading/trailing whitespace even into a single
|
|
# variable — confirmed live, a password with a leading/trailing
|
|
# space (copy-pasted from a password manager, a stray keystroke)
|
|
# got quietly trimmed on the way in, so the credentials file ended
|
|
# up holding a DIFFERENT password than the one actually set on the
|
|
# Samba account. That surfaces as a cryptic mount failure, not an
|
|
# obvious "wrong password" — nothing here could tell the two apart.
|
|
echo -n " ${prompt}: " >&2
|
|
IFS= read -r -s pw1; echo "" >&2
|
|
echo -n " Confirm: " >&2
|
|
IFS= read -r -s pw2; echo "" >&2
|
|
if [ -n "$pw1" ] && [ "$pw1" = "$pw2" ]; then
|
|
# Length only, never the password itself — lets you catch a
|
|
# silently-stripped character (or a typo) yourself before the
|
|
# mount attempt fails with an unhelpful error.
|
|
echo " Captured (${#pw1} characters)." >&2
|
|
printf '%s' "$pw1"
|
|
return 0
|
|
fi
|
|
echo " Passwords didn't match or were empty — try again." >&2
|
|
done
|
|
}
|
|
|
|
# ── Local mount + fstab ─────────────────────────────────────────────────────
|
|
_vdm_mount_local() {
|
|
local host="$1" share_name="$2" mount_point="$3" label="$4" smb_user="$5" smb_pass="$6"
|
|
|
|
command -v mount.cifs >/dev/null 2>&1 || apt-get install -y cifs-utils -qq
|
|
|
|
mkdir -p "$mount_point"
|
|
|
|
# Credentials file, not guest/inline password — root-only, matching
|
|
# tools/mount-network-drive.sh's existing convention for SMB creds.
|
|
local creds_file="/etc/samba/credentials.vpn-data-mount-${label}"
|
|
mkdir -p /etc/samba
|
|
cat > "$creds_file" << CREDS
|
|
username=${smb_user}
|
|
password=${smb_pass}
|
|
CREDS
|
|
chmod 600 "$creds_file"
|
|
chown root:root "$creds_file"
|
|
|
|
# sec=ntlmssp explicitly — see the file header on errno 79/ENOKEY.
|
|
local opts="credentials=${creds_file},sec=ntlmssp,uid=$(id -u "$ACTUAL_USER"),gid=$(id -g "$ACTUAL_USER"),iocharset=utf8,nofail,_netdev"
|
|
local share="//${host}/${share_name}"
|
|
|
|
log_info "Testing mount..."
|
|
if mount -t cifs -o "$opts" "$share" "$mount_point"; then
|
|
log_success "Mounted at $mount_point"
|
|
else
|
|
log_error "Mount failed for [$share_name] — check the username/password and that the home box's share actually allows this account."
|
|
rmdir "$mount_point" 2>/dev/null || true
|
|
rm -f "$creds_file"
|
|
return 1
|
|
fi
|
|
|
|
if grep -qs "$mount_point" /etc/fstab; then
|
|
log_warning "$mount_point already in /etc/fstab — skipping fstab entry."
|
|
return 0
|
|
fi
|
|
local bk="/etc/fstab.backup.$(date +%Y%m%d-%H%M%S)"
|
|
cp /etc/fstab "$bk"
|
|
{
|
|
echo ""
|
|
echo "${_VDM_TAG_PREFIX} ${label} — ${host}:${share_name} -> ${mount_point}"
|
|
printf '%-40s %-25s %-6s %s 0 0\n' "$share" "$mount_point" "cifs" "$opts"
|
|
} >> /etc/fstab
|
|
log_success "Added to /etc/fstab (backup: $(basename "$bk"))"
|
|
}
|
|
|
|
# ── Parse a selection like "1", "1,3", "1-3", "1 3 5" into 1-based indices ──
|
|
# Prints one index per line. Silently drops anything that doesn't look like
|
|
# a number or a range — the caller validates indices against the actual
|
|
# list length.
|
|
_vdm_parse_selection() {
|
|
local input="$1" token start end i
|
|
for token in $(echo "$input" | tr ',' ' '); do
|
|
if [[ "$token" =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
|
start="${BASH_REMATCH[1]}"; end="${BASH_REMATCH[2]}"
|
|
for ((i = start; i <= end; i++)); do echo "$i"; done
|
|
elif [[ "$token" =~ ^[0-9]+$ ]]; then
|
|
echo "$token"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# ── One home box, one or more shares from it ────────────────────────────────
|
|
_vdm_add_mount() {
|
|
echo ""
|
|
local HOST_INPUT="" HOST="" SSH_USER=""
|
|
prompt_text " Home box's NetBird IP or an already-named host (check 'netbird status' on that box):" "" HOST_INPUT
|
|
if [ -z "$HOST_INPUT" ]; then
|
|
log_warning "No host entered — cancelling."
|
|
return 1
|
|
fi
|
|
_vdm_resolve_host "$HOST_INPUT"
|
|
HOST="$RESOLVED_HOST"
|
|
|
|
prompt_text " SSH username on the home box:" "$ACTUAL_USER" SSH_USER
|
|
|
|
_vdm_ensure_ssh_trust "$SSH_USER" "$HOST" || return 1
|
|
|
|
# Pure convenience on top of the /etc/hosts naming above (which is what
|
|
# actually makes the mount itself usable by name) — an SSH Host alias
|
|
# additionally skips typing the username for interactive `ssh` use.
|
|
if [[ ! "$HOST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] \
|
|
&& declare -F add_ssh_host_alias >/dev/null 2>&1 \
|
|
&& declare -F ssh_host_alias_exists >/dev/null 2>&1 \
|
|
&& ! ssh_host_alias_exists "$HOST"; then
|
|
local ADD_ALIAS=""
|
|
prompt_yn " Also add '$HOST' as an SSH alias (ssh $HOST, no username needed)? (y/n):" "y" ADD_ALIAS
|
|
[[ "$ADD_ALIAS" =~ ^[Yy]$ ]] && add_ssh_host_alias "$HOST" "$HOST_INPUT" "$SSH_USER" "22"
|
|
fi
|
|
|
|
log_info "Reading Samba shares already configured on $HOST (read-only)..."
|
|
local shares_raw
|
|
shares_raw="$(_vdm_list_remote_shares "$SSH_USER" "$HOST")"
|
|
if [ -z "$shares_raw" ]; then
|
|
log_warning "No Samba shares found on $HOST (or /etc/samba/smb.conf couldn't be read). Set up a share there first, the normal way, then re-run this."
|
|
return 1
|
|
fi
|
|
|
|
local share_names=() share_paths=()
|
|
while IFS='|' read -r sname spath; do
|
|
[ -z "$sname" ] && continue
|
|
share_names+=("$sname")
|
|
share_paths+=("$spath")
|
|
done <<< "$shares_raw"
|
|
|
|
echo ""
|
|
echo " Samba shares found on $HOST:"
|
|
local i
|
|
for i in "${!share_names[@]}"; do
|
|
printf " %d) %-20s %s\n" "$((i + 1))" "${share_names[$i]}" "${share_paths[$i]}"
|
|
done
|
|
echo ""
|
|
local SELECTION=""
|
|
prompt_text " Which one(s)? e.g. '1' or '1,3' or '1-3' or '1 3 5':" "" SELECTION
|
|
if [ -z "$SELECTION" ]; then
|
|
log_warning "Nothing selected — cancelling."
|
|
return 1
|
|
fi
|
|
|
|
local indices=()
|
|
while IFS= read -r i; do indices+=("$i"); done < <(_vdm_parse_selection "$SELECTION")
|
|
if [ "${#indices[@]}" -eq 0 ]; then
|
|
log_warning "Couldn't parse a selection from '$SELECTION' — cancelling."
|
|
return 1
|
|
fi
|
|
|
|
# Credentials asked once, reused for every share picked here — the
|
|
# common case is one personal account with access to several shares.
|
|
# Re-run this for a share needing a different account.
|
|
local SMB_USER=""
|
|
prompt_text " Samba username to connect with:" "$SSH_USER" SMB_USER
|
|
local SMB_PASS=""
|
|
SMB_PASS="$(_vdm_find_existing_smb_password "$HOST" "$SMB_USER")"
|
|
if [ -n "$SMB_PASS" ]; then
|
|
log_info "Reusing the Samba password already saved for '$SMB_USER' on $HOST from an earlier mount."
|
|
else
|
|
SMB_PASS="$(_vdm_prompt_password "Samba password for '$SMB_USER'")"
|
|
fi
|
|
|
|
local picked_any=false idx arr_i this_share this_path LABEL MOUNT_POINT
|
|
for idx in "${indices[@]}"; do
|
|
arr_i=$((idx - 1))
|
|
if [ "$arr_i" -lt 0 ] || [ "$arr_i" -ge "${#share_names[@]}" ]; then
|
|
log_warning "$idx isn't one of the listed shares — skipping."
|
|
continue
|
|
fi
|
|
this_share="${share_names[$arr_i]}"
|
|
this_path="${share_paths[$arr_i]}"
|
|
|
|
echo ""
|
|
log_info "Setting up: [$this_share] -> $this_path"
|
|
|
|
LABEL=""
|
|
while true; do
|
|
prompt_text " Local label for this mount:" "$this_share" LABEL
|
|
LABEL="$(echo "$LABEL" | tr -cs 'a-zA-Z0-9-' '-' | sed 's/^-*//;s/-*$//')"
|
|
if [ -z "$LABEL" ]; then
|
|
log_warning "Label can't be empty."; continue
|
|
fi
|
|
if grep -q "^${_VDM_TAG_PREFIX} ${LABEL} " /etc/fstab 2>/dev/null; then
|
|
log_warning "Label '$LABEL' is already used — pick another."; continue
|
|
fi
|
|
break
|
|
done
|
|
|
|
MOUNT_POINT=""
|
|
prompt_text " Local mount point:" "/mnt/${LABEL}" MOUNT_POINT
|
|
|
|
if _vdm_mount_local "$HOST" "$this_share" "$MOUNT_POINT" "$LABEL" "$SMB_USER" "$SMB_PASS"; then
|
|
# Read by callers like services/filebrowser.sh/audiobookshelf.sh/
|
|
# emby.sh that chain into this service and want to default
|
|
# their own "which directory" prompt to whatever was just
|
|
# mounted. Last one wins if several were picked in this run.
|
|
VDM_LAST_MOUNT_POINT="$MOUNT_POINT"
|
|
picked_any=true
|
|
fi
|
|
done
|
|
|
|
if [ "$picked_any" = true ]; then
|
|
echo ""
|
|
log_success "Done."
|
|
echo " Manage this and other network mounts anytime with:"
|
|
echo " sudo bash tools/mount-network-drive.sh"
|
|
fi
|
|
}
|
|
|
|
install_vpn-data-mount() {
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════════╗"
|
|
echo "║ VPN Data Mount — mount existing SMB shares from a home ║"
|
|
echo "║ box over NetBird (read-only — nothing changes there) ║"
|
|
echo "╚══════════════════════════════════════════════════════════╝"
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo "[DRY-RUN] Would offer to name a raw IP in /etc/hosts for reuse (SSH + this mount)"
|
|
echo "[DRY-RUN] Would test/set up passwordless SSH to a home box over its NetBird IP"
|
|
echo "[DRY-RUN] Would read-only list the home box's existing Samba shares (never writes there)"
|
|
echo "[DRY-RUN] Would let you pick one or more by number and mount them locally over CIFS"
|
|
echo "[DRY-RUN] Would add each to /etc/fstab with a root-only credentials file (not guest)"
|
|
echo "[DRY-RUN] Repeatable — can be run again for additional home boxes"
|
|
return 0
|
|
fi
|
|
|
|
# Every prompt below (home box IP, share selection, ...) has no sane
|
|
# unattended default — unlike most services here, there's no reasonable
|
|
# value to fall back to. Skip outright rather than let prompt_text's
|
|
# always-blank UNATTENDED behavior spin something forever.
|
|
if [ "$UNATTENDED" = true ]; then
|
|
log_info "Skipping — needs interactive input (home box IP, share selection, ...). Run 'sudo ./setup.sh vpn-data-mount' without --unattended."
|
|
return 0
|
|
fi
|
|
|
|
_vdm_list_existing
|
|
|
|
while true; do
|
|
local ADD=""
|
|
prompt_yn "Connect to a home box and mount some of its shares now? (y/n):" "y" ADD
|
|
[[ "$ADD" =~ ^[Yy]$ ]] || break
|
|
|
|
_vdm_add_mount
|
|
|
|
local AGAIN=""
|
|
prompt_yn "Connect to another (different) home box? (y/n):" "n" AGAIN
|
|
[[ "$AGAIN" =~ ^[Yy]$ ]] || break
|
|
done
|
|
}
|
|
|
|
[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_vpn-data-mount
|