#!/bin/bash
# services/samba.sh — Samba (SMB/CIFS) file sharing: shares, users, passwords.
# Part of the modular post-install system (sourced by setup.sh).
#
# Can also be run standalone on any machine:
#   sudo bash samba.sh
#
# Samba is a SYSTEM install (apt package + native smbd/nmbd services), NOT a
# docker-compose service — same shape as services/crowdsec.sh. There is no
# ~/docker/samba compose stack; we only create a docs-only folder there with
# a README pointing at the real config under /etc/samba/smb.conf. This is
# the SERVER side — for mounting an existing remote Samba share instead, see
# services/vpn-data-mount.sh (deliberately the opposite: reads an existing
# smb.conf over SSH, never installs Samba, never creates or resets a share
# password).

# ── Standalone bootstrap ──────────────────────────────────────────────────────
# Detected when the script is executed directly rather than sourced by setup.sh.
# Sets up helpers and globals, then defers execution until after the function
# definition at the bottom of this file.
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
        # Full repo present — use the real helpers (picks up ~/docker/.config too)
        # shellcheck source=../lib/common.sh
        source "$_COMMON"
    else
        # One-off copy — inline minimal stubs so the script works without the repo
        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; }

        ensure_docker_dir_ownership() {
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$@" 2>/dev/null || true
        }

        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}'"
        }

        generate_password() {
            local _len="${1:-32}"
            tr -dc 'A-Za-z0-9' < /dev/urandom | head -c "$_len"
        }

        ensure_ufw_enabled() {
            command -v ufw &>/dev/null || return 0
            ufw status 2>/dev/null | grep -q "Status: active" && return 0
            local _ssh_port
            _ssh_port="$(grep -iE '^[[:space:]]*Port[[:space:]]+[0-9]+' /etc/ssh/sshd_config 2>/dev/null \
                | tail -1 | awk '{print $2}')"
            _ssh_port="${_ssh_port:-22}"
            ufw allow "${_ssh_port}/tcp" comment 'SSH' >/dev/null 2>&1
            ufw --force enable >/dev/null 2>&1
            log_success "UFW enabled (SSH on port ${_ssh_port} allowed first, so this won't lock you out)."
        }

        write_readme() {
            local _dir="$1"; shift
            mkdir -p "$_dir"
            cat > "$_dir/README.md"
        }
        backup_if_exists() {
            local _file="$1"
            [ -f "$_file" ] || return 0
            cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
        }
    fi

    # Globals — ACTUAL_USER/ACTUAL_HOME must come before DOCKER_DIR
    # ($HOME under sudo is /root, not the real user's home)
    ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
    ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" 2>/dev/null | cut -d: -f6 || echo "${HOME:-/root}")"
    DOCKER_DIR="${DOCKER_DIR:-$ACTUAL_HOME/docker}"
    DRY_RUN="${DRY_RUN:-false}"
    UNATTENDED="${UNATTENDED:-false}"

    register_service() { :; }   # no-op — no wizard to register into
    _RUN_STANDALONE=1
fi
# ─────────────────────────────────────────────────────────────────────────────

register_service samba utilities "Samba file sharing (SMB/CIFS) — shares, users, passwords"

install_samba() {
    local SMB_CONF="/etc/samba/smb.conf"
    local DOCS_DIR="$DOCKER_DIR/samba"

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would install samba (smbd/nmbd) if not already present"
        echo "[DRY-RUN] Would show any shares this installer already manages"
        echo "[DRY-RUN] Would prompt to add one or more shares (path, guest-or-authenticated, users)"
        echo "[DRY-RUN] Would create a system Linux account + Samba password for any new user"
        echo "[DRY-RUN] Would append share stanzas to $SMB_CONF, validate with testparm, restart smbd/nmbd"
        echo "[DRY-RUN] Would open UFW for SMB (137/138 udp, 139/445 tcp) — scoped to the LAN by default"
        echo "[DRY-RUN] Would write $DOCS_DIR/README.md (docs only — Samba itself runs natively, not in Docker)"
        return 0
    fi

    if ! command -v smbd &>/dev/null; then
        log_info "Installing Samba..."
        apt-get update -y
        apt-get install -y samba || { log_error "Samba install failed"; return 1; }
        log_success "Samba installed"
    else
        log_success "Samba already installed"
    fi

    backup_if_exists "$SMB_CONF"

    if grep -q '^# ubuntu-post-install:share:' "$SMB_CONF" 2>/dev/null; then
        echo ""
        log_info "Shares already managed by this installer:"
        grep '^# ubuntu-post-install:share:' "$SMB_CONF" | sed 's/^# ubuntu-post-install:share:/  - /'
    fi

    echo ""
    local _added_any=false
    while true; do
        local ADD_SHARE=""
        prompt_yn "Add a Samba share now? (y/n):" "y" ADD_SHARE
        [[ "$ADD_SHARE" =~ ^[Yy]$ ]] || break
        _samba_add_share "$SMB_CONF" && _added_any=true
        echo ""
    done

    if [ "$_added_any" = true ]; then
        log_info "Validating smb.conf..."
        if testparm -s "$SMB_CONF" &>/dev/null; then
            systemctl restart smbd 2>/dev/null
            systemctl restart nmbd 2>/dev/null   # NetBIOS name resolution — some Samba packages split this out
            log_success "smbd/nmbd restarted with the new configuration"
        else
            log_error "testparm reports smb.conf is invalid — NOT restarting smbd/nmbd."
            log_error "Check manually: sudo testparm -s $SMB_CONF"
            return 1
        fi
    else
        log_info "No shares added this run."
    fi

    _samba_configure_firewall

    mkdir -p "$DOCS_DIR"
    ensure_docker_dir_ownership "$DOCS_DIR"
    write_readme "$DOCS_DIR" << MD
# Samba

Samba runs natively on this box (not in Docker) — the real config is
\`/etc/samba/smb.conf\`, managed by \`systemctl\`. This folder just holds this
README; there's no compose stack here.

## Manage

\`\`\`bash
sudo testparm -s              # validate smb.conf before restarting
sudo systemctl restart smbd nmbd
sudo systemctl status smbd
\`\`\`

## Shares

Re-run \`sudo ./setup.sh samba\` (or \`sudo bash services/samba.sh\` standalone)
to add another share or another user — existing shares/users are left alone.

Each share this installer wrote is marked in smb.conf with a
\`# ubuntu-post-install:share:<name>\` comment right above its \`[<name>]\`
stanza, so you can find (or hand-edit / remove) them later.

## Users

Samba users need BOTH a Linux account and a separate Samba password
(\`smbpasswd\`) — they are not the same credential. This installer creates a
system account (\`useradd --system --no-create-home\`, no shell login) for
any username that doesn't already exist as a Linux user, adds it to the
\`sambashare\` group, and sets its Samba password with \`smbpasswd\`.

\`\`\`bash
sudo smbpasswd <username>        # change an existing user's Samba password
sudo pdbedit -L                  # list all Samba users
sudo smbpasswd -x <username>     # remove a user from Samba (leaves the Linux account alone)
\`\`\`

## Connecting

- Windows: \`\\\\<server-ip>\\<share-name>\`
- macOS Finder: Go -> Connect to Server -> \`smb://<server-ip>/<share-name>\`
- Linux: \`smbclient //<server-ip>/<share-name> -U <username>\` or mount with
  \`mount.cifs\` / \`cifs-utils\` (already installed by \`services/base.sh\`).

## Firewall

SMB (137/138 UDP, 139/445 TCP) should almost never be exposed to the public
internet — this installer scopes the UFW rule to your LAN subnet by default.
Check what's currently allowed with \`sudo ufw status | grep -E '13[7-9]|445'\`.
MD

    log_success "Samba configured. Re-run 'sudo ./setup.sh samba' any time to add another share or user."
}

# Appends one [share] stanza to smb.conf. Returns non-zero (and adds nothing)
# on a blank/duplicate name so the caller's "did we actually add one" tracking
# stays accurate.
_samba_add_share() {
    local _conf="$1"
    local NAME="" SHARE_PATH="" GUEST=""

    prompt_text "  Share name (letters/numbers/hyphens/underscores, e.g. media):" "" NAME
    NAME="$(echo "$NAME" | tr -cd 'A-Za-z0-9_-')"
    if [[ -z "$NAME" ]]; then
        log_warning "Share name required — skipping."
        return 1
    fi
    if grep -q "^\[$NAME\]\$" "$_conf" 2>/dev/null; then
        log_warning "A share named [$NAME] already exists in smb.conf — skipping."
        log_warning "Edit $_conf by hand to change it, or pick a different name."
        return 1
    fi

    local DEFAULT_PATH="/srv/samba/$NAME"
    prompt_text "  Path to share [$DEFAULT_PATH]:" "$DEFAULT_PATH" SHARE_PATH
    SHARE_PATH="${SHARE_PATH:-$DEFAULT_PATH}"
    SHARE_PATH="${SHARE_PATH/#\~/$ACTUAL_HOME}"
    mkdir -p "$SHARE_PATH"

    prompt_yn "  Allow guest (no password) access to '$NAME'? (y/n):" "n" GUEST

    local VALID_USERS=""
    if [[ ! "$GUEST" =~ ^[Yy]$ ]]; then
        echo "  Enter Samba usernames to grant access to '$NAME' (blank to stop):"
        while true; do
            local SUSER=""
            prompt_text "  Username:" "" SUSER
            [[ -z "$SUSER" ]] && break
            _samba_ensure_user "$SUSER"
            VALID_USERS="${VALID_USERS:+$VALID_USERS }$SUSER"
        done
        if [[ -z "$VALID_USERS" ]]; then
            log_warning "No users added and guest access declined — '$NAME' will be inaccessible until you add a user (re-run this installer, or edit smb.conf by hand)."
        fi
    fi

    getent group sambashare >/dev/null 2>&1 || groupadd sambashare
    if [[ "$GUEST" =~ ^[Yy]$ ]]; then
        chmod 0777 "$SHARE_PATH"
    else
        chgrp sambashare "$SHARE_PATH" 2>/dev/null || true
        chmod 0770 "$SHARE_PATH"
    fi

    {
        echo ""
        echo "# ubuntu-post-install:share:$NAME"
        echo "[$NAME]"
        echo "   path = $SHARE_PATH"
        echo "   browseable = yes"
        echo "   read only = no"
        if [[ "$GUEST" =~ ^[Yy]$ ]]; then
            echo "   guest ok = yes"
        else
            echo "   guest ok = no"
            [[ -n "$VALID_USERS" ]] && echo "   valid users = $VALID_USERS"
        fi
    } >> "$_conf"

    log_success "Share '$NAME' -> $SHARE_PATH added to smb.conf"
}

# Creates the Linux system account (if missing) and sets a Samba password for
# it. Samba users need BOTH — a Linux account and a separate smbpasswd entry
# — they are not the same credential, and smbpasswd -a fails outright against
# a username with no matching Linux account at all.
_samba_ensure_user() {
    local _user="$1"

    if ! id "$_user" &>/dev/null; then
        log_info "Linux account '$_user' doesn't exist — creating a system account (no shell login, no home dir)."
        useradd --system --no-create-home --shell /usr/sbin/nologin "$_user"
    fi

    getent group sambashare >/dev/null 2>&1 || groupadd sambashare
    usermod -aG sambashare "$_user"

    if pdbedit -L 2>/dev/null | cut -d: -f1 | grep -qx "$_user"; then
        log_info "Samba password already set for '$_user' — leaving as-is (change it later with: sudo smbpasswd $_user)."
        return 0
    fi

    local _pass _entered=""
    _pass="$(generate_password 16)"
    prompt_text "  Samba password for '$_user' [$_pass]:" "$_pass" _entered
    _pass="${_entered:-$_pass}"

    if printf '%s\n%s\n' "$_pass" "$_pass" | smbpasswd -s -a "$_user" >/dev/null 2>&1 \
        && smbpasswd -e "$_user" >/dev/null 2>&1; then
        log_success "Samba user '$_user' set — password: $_pass (write this down, it isn't stored anywhere else)"
    else
        log_warning "Failed to set Samba password for '$_user' — set it manually: sudo smbpasswd $_user"
    fi
}

# SMB should almost never face the public internet — scope the UFW rule to
# the LAN by default (LAN-subnet detection borrowed from the same pattern
# services/asterisk.sh uses for its VLAN/local-network prompt).
_samba_configure_firewall() {
    command -v ufw &>/dev/null || {
        log_warning "ufw not installed — if you use a firewall, open TCP 139/445 and UDP 137/138 for SMB (LAN only, never the internet)."
        return 0
    }

    echo ""
    local DETECTED_NETS DEFAULT_SUBNET=""
    DETECTED_NETS="$(ip -o -f inet addr show scope global 2>/dev/null \
        | awk '{print $2, $4}' \
        | grep -Ev '^(docker|br-|veth|tun|tap|wg)' \
        | awk '{ split($2,a,"/"); split(a[1],o,"."); print o[1]"."o[2]"."o[3]".0/"a[2] }' \
        | sort -u)"
    DEFAULT_SUBNET="$(echo "$DETECTED_NETS" | head -1)"

    local RESTRICT_LAN=""
    prompt_yn "Restrict Samba access to your local network only (recommended — SMB should never face the internet)? (y/n):" "y" RESTRICT_LAN

    if [[ "$RESTRICT_LAN" =~ ^[Yy]$ ]]; then
        local SUBNET=""
        prompt_text "  LAN subnet to allow (CIDR)${DEFAULT_SUBNET:+ [$DEFAULT_SUBNET]}:" "$DEFAULT_SUBNET" SUBNET
        SUBNET="${SUBNET:-$DEFAULT_SUBNET}"
        if [[ -z "$SUBNET" ]]; then
            log_warning "No subnet given — skipping UFW rules. Open them manually if needed."
            return 0
        fi
        local p
        for p in 137 138; do
            ufw allow from "$SUBNET" to any port "$p" proto udp comment "Samba (LAN)" >/dev/null 2>&1
        done
        for p in 139 445; do
            ufw allow from "$SUBNET" to any port "$p" proto tcp comment "Samba (LAN)" >/dev/null 2>&1
        done
        log_success "UFW: Samba opened to $SUBNET only"
    else
        log_warning "Opening Samba to ALL sources — not recommended, SMB has a long history of remote exploits."
        ufw allow 137/udp comment "Samba" >/dev/null 2>&1
        ufw allow 138/udp comment "Samba" >/dev/null 2>&1
        ufw allow 139/tcp comment "Samba" >/dev/null 2>&1
        ufw allow 445/tcp comment "Samba" >/dev/null 2>&1
        log_success "UFW: Samba opened (unrestricted)"
    fi
    ensure_ufw_enabled
}

[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_samba
