Add samba service: shares, dedicated users, LAN-scoped firewall
New services/samba.sh, following the non-Docker service shape (services/crowdsec.sh) since Samba runs natively (smbd/nmbd), not in a container: - Installs the samba package if missing - Prompts to add one or more shares (path, guest vs. authenticated) - For authenticated shares, creates a system Linux account (if one doesn't already exist) and a separate Samba password via smbpasswd for each user, adds them to a sambashare group - Appends share stanzas to /etc/samba/smb.conf (tagged with a # ubuntu-post-install:share:<name> marker for later discovery), validates with testparm before restarting smbd/nmbd - Opens UFW for SMB (137/138 udp, 139/445 tcp), scoped to the detected LAN subnet by default rather than the whole internet - Writes a docs-only README under ~/docker/samba (no compose stack) Registered under `utilities`, with an is_installed()/install_count() entry in setup.sh (command -v smbd, matching the glow/crowdsec pattern for non-Docker services) and a README.md Services table entry. Also wired as an optional nudge into services/base.sh, alongside the existing Caddy/CrowdSec/NetBird prompts — offered during the base install but not unconditional, since (unlike net-tools/ncdu) it needs real input — a share path and at least one user — to do anything useful, so it defaults to declined rather than accepted.
This commit is contained in:
@@ -186,7 +186,7 @@ a ready-to-copy Caddy config snippet to `~/docker/caddy-snippets/`.
|
|||||||
|-------|---------|
|
|-------|---------|
|
||||||
| `base` | `net-tools`, `ncdu`, `git`, `curl`, `wget`, `htop`, `tree`, `zip`/`unzip`, `ca-certificates`, `gnupg`, `jq`, `rsync`; `glow` (terminal markdown reader, Charm apt repo); Docker CE + Compose plugin; `openssh-server` with GitHub/Launchpad SSH key import, optional password-auth lockdown, and SSH Host aliases; optional NetBird overlay network |
|
| `base` | `net-tools`, `ncdu`, `git`, `curl`, `wget`, `htop`, `tree`, `zip`/`unzip`, `ca-certificates`, `gnupg`, `jq`, `rsync`; `glow` (terminal markdown reader, Charm apt repo); Docker CE + Compose plugin; `openssh-server` with GitHub/Launchpad SSH key import, optional password-auth lockdown, and SSH Host aliases; optional NetBird overlay network |
|
||||||
| `homelab` | `caddy`, `crowdsec`, `authelia`, `homeassistant`, `asterisk` (own dedicated coturn for TURN/STUN — see `mattermost` below for the other coturn-owning service), `pstn-trunk`, `sms-inbound`, `security-dashboard`, `sunshine`, `vpn-data-mount` (mount existing SMB shares from a NetBird-connected home box — SSH trust bootstrap, then read-only discovery of shares already configured there; never writes to the home box's Samba config; repeatable, pick from any number of a home box's shares in one pass; optional per-share [gocryptfs decrypt layer](#client-side-encryption-for-vpn-data-mount) so the VPS only ever handles ciphertext) |
|
| `homelab` | `caddy`, `crowdsec`, `authelia`, `homeassistant`, `asterisk` (own dedicated coturn for TURN/STUN — see `mattermost` below for the other coturn-owning service), `pstn-trunk`, `sms-inbound`, `security-dashboard`, `sunshine`, `vpn-data-mount` (mount existing SMB shares from a NetBird-connected home box — SSH trust bootstrap, then read-only discovery of shares already configured there; never writes to the home box's Samba config; repeatable, pick from any number of a home box's shares in one pass; optional per-share [gocryptfs decrypt layer](#client-side-encryption-for-vpn-data-mount) so the VPS only ever handles ciphertext) |
|
||||||
| `utilities` | `actualbudget`, `ai-gpu`, `ai-stack`, `archivebox`, `beszel` (lightweight server + Docker monitoring — CPU/RAM/disk/network, auto-discovers running containers via the Docker socket; complements Gatus rather than replacing it — Gatus is a black-box HTTP check, Beszel is white-box host/process monitoring), `beszel-agent` (agent-only Beszel install for a remote/homelab box reporting to a hub elsewhere — connects outbound over HTTPS, no VPN/port-forwarding/FQDN needed on that box), `changedetection`, `ddclient`, `filebrowser`, `fmd`, `garage` (self-hosted S3-compatible object storage, single node — MinIO CE's actively-maintained replacement), `garage-webui` (browser-based bucket/object browser for an existing `garage` install — folders/files view, the same kind of thing Backblaze's own web console gives you), `gatus`, `gitea` (self-hosted Git server — raw local clones plus optional two-way GitHub mirror sync, standalone from the `ai-stack` bundle's own Gitea container), `homebox`, `iopaint`, `joplin`, `koha`, `magicmirror`, `mail-archiver`, `mattermost`, `mealie`, `meshcentral`, `n8n`, `nextcloud`, `ntfy`, `onlyoffice`, `paintplus`, `pihole` (standalone DNS ad/tracker blocking — not wired into any VPN's DNS push), `portainer`, `pressbooks` (self-hosted book platform — WordPress Multisite, drag-and-drop chapter editing, PDF export via PrinceXML/DocRaptor, Authelia-gated), `rustdesk`, `stirling-pdf`, `syncthing`, `traccar`, `unifi`, `uptimekuma`, `vaultwarden`, `watchyourlan`, `watchtower`, `wg-easy`, `wordpress` (multi-site, dedicated MariaDB per site — blogs, business sites, e-commerce via WooCommerce) |
|
| `utilities` | `actualbudget`, `ai-gpu`, `ai-stack`, `archivebox`, `beszel` (lightweight server + Docker monitoring — CPU/RAM/disk/network, auto-discovers running containers via the Docker socket; complements Gatus rather than replacing it — Gatus is a black-box HTTP check, Beszel is white-box host/process monitoring), `beszel-agent` (agent-only Beszel install for a remote/homelab box reporting to a hub elsewhere — connects outbound over HTTPS, no VPN/port-forwarding/FQDN needed on that box), `changedetection`, `ddclient`, `filebrowser`, `fmd`, `garage` (self-hosted S3-compatible object storage, single node — MinIO CE's actively-maintained replacement), `garage-webui` (browser-based bucket/object browser for an existing `garage` install — folders/files view, the same kind of thing Backblaze's own web console gives you), `gatus`, `gitea` (self-hosted Git server — raw local clones plus optional two-way GitHub mirror sync, standalone from the `ai-stack` bundle's own Gitea container), `homebox`, `iopaint`, `joplin`, `koha`, `magicmirror`, `mail-archiver`, `mattermost`, `mealie`, `meshcentral`, `n8n`, `nextcloud`, `ntfy`, `onlyoffice`, `paintplus`, `pihole` (standalone DNS ad/tracker blocking — not wired into any VPN's DNS push), `portainer`, `pressbooks` (self-hosted book platform — WordPress Multisite, drag-and-drop chapter editing, PDF export via PrinceXML/DocRaptor, Authelia-gated), `rustdesk`, `samba` (SMB/CIFS file sharing — shares, dedicated Samba users/passwords, LAN-scoped firewall by default; also offered as an optional nudge from `base`), `stirling-pdf`, `syncthing`, `traccar`, `unifi`, `uptimekuma`, `vaultwarden`, `watchyourlan`, `watchtower`, `wg-easy`, `wordpress` (multi-site, dedicated MariaDB per site — blogs, business sites, e-commerce via WooCommerce) |
|
||||||
| `media` | `arm`, `audiobookshelf`, `calibre-web`, `emby`, `immich`, `jellyfin`, `lyrion` |
|
| `media` | `arm`, `audiobookshelf`, `calibre-web`, `emby`, `immich`, `jellyfin`, `lyrion` |
|
||||||
| `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` |
|
| `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` |
|
||||||
| `gaming` | `drum-rhythm-game`, `js99er`, `kyber-launcher`, `kyber-server`, `minecraft`, `wolf`, `wolf-pair` |
|
| `gaming` | `drum-rhythm-game`, `js99er`, `kyber-launcher`, `kyber-server`, `minecraft`, `wolf`, `wolf-pair` |
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ install_base() {
|
|||||||
echo "[DRY-RUN] Would offer to mount SMB data from a NetBird-connected home box (if NetBird is present)"
|
echo "[DRY-RUN] Would offer to mount SMB data from a NetBird-connected home box (if NetBird is present)"
|
||||||
echo "[DRY-RUN] Would offer Caddy reverse proxy install (full repo only)"
|
echo "[DRY-RUN] Would offer Caddy reverse proxy install (full repo only)"
|
||||||
echo "[DRY-RUN] Would offer CrowdSec intrusion prevention install (full repo only)"
|
echo "[DRY-RUN] Would offer CrowdSec intrusion prevention install (full repo only)"
|
||||||
|
echo "[DRY-RUN] Would offer Samba (SMB/CIFS) file sharing install (full repo only)"
|
||||||
echo "[DRY-RUN] Would offer to add SSH Host aliases to ~/.ssh/config"
|
echo "[DRY-RUN] Would offer to add SSH Host aliases to ~/.ssh/config"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -80,6 +81,14 @@ install_base() {
|
|||||||
_base_setup_crowdsec
|
_base_setup_crowdsec
|
||||||
cd "$_BASE_PWD" 2>/dev/null || true
|
cd "$_BASE_PWD" 2>/dev/null || true
|
||||||
|
|
||||||
|
# ── Samba ────────────────────────────────────────────────────────────────
|
||||||
|
# Same nudge-not-mandatory shape as Caddy/CrowdSec above: fully optional,
|
||||||
|
# independently re-runnable later via `sudo ./setup.sh samba`. Defaults to
|
||||||
|
# n (unlike Caddy/CrowdSec) because it needs real input to be useful — a
|
||||||
|
# share path and at least one user — not just "yes, with sane defaults".
|
||||||
|
_base_setup_samba
|
||||||
|
cd "$_BASE_PWD" 2>/dev/null || true
|
||||||
|
|
||||||
# ── SSH Host aliases ─────────────────────────────────────────────────────
|
# ── SSH Host aliases ─────────────────────────────────────────────────────
|
||||||
_base_setup_ssh_aliases
|
_base_setup_ssh_aliases
|
||||||
|
|
||||||
@@ -329,6 +338,22 @@ _base_setup_crowdsec() {
|
|||||||
install_crowdsec
|
install_crowdsec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_base_setup_samba() {
|
||||||
|
if command -v smbd &>/dev/null; then
|
||||||
|
log_info "Samba already installed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Only available when the full repo is sourced (setup.sh loads every
|
||||||
|
# services/*.sh up front) — a standalone copy of base.sh doesn't have
|
||||||
|
# install_samba, so skip silently rather than error.
|
||||||
|
declare -F install_samba &>/dev/null || return 0
|
||||||
|
|
||||||
|
local INSTALL_SAMBA=""
|
||||||
|
prompt_yn "Install Samba (SMB/CIFS) file sharing now — shares, users, passwords? (y/n):" "n" INSTALL_SAMBA
|
||||||
|
[[ "$INSTALL_SAMBA" =~ ^[Yy]$ ]] || return 0
|
||||||
|
install_samba
|
||||||
|
}
|
||||||
|
|
||||||
_base_setup_ssh_aliases() {
|
_base_setup_ssh_aliases() {
|
||||||
local ADD_ALIAS=""
|
local ADD_ALIAS=""
|
||||||
prompt_yn "Add an SSH Host alias now ('ssh myserver' instead of 'ssh user@1.2.3.4')? (y/n):" "n" ADD_ALIAS
|
prompt_yn "Add an SSH Host alias now ('ssh myserver' instead of 'ssh user@1.2.3.4')? (y/n):" "n" ADD_ALIAS
|
||||||
|
|||||||
@@ -0,0 +1,365 @@
|
|||||||
|
#!/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
|
||||||
@@ -121,6 +121,7 @@ is_installed() {
|
|||||||
base) command -v ncdu >/dev/null 2>&1 ;;
|
base) command -v ncdu >/dev/null 2>&1 ;;
|
||||||
glow) command -v glow >/dev/null 2>&1 ;;
|
glow) command -v glow >/dev/null 2>&1 ;;
|
||||||
crowdsec) command -v cscli >/dev/null 2>&1 ;;
|
crowdsec) command -v cscli >/dev/null 2>&1 ;;
|
||||||
|
samba) command -v smbd >/dev/null 2>&1 ;;
|
||||||
security-dashboard) [ -f /opt/security-dashboard/app.py ] ;;
|
security-dashboard) [ -f /opt/security-dashboard/app.py ] ;;
|
||||||
kdeconnect) command -v kdeconnect >/dev/null 2>&1 ;;
|
kdeconnect) command -v kdeconnect >/dev/null 2>&1 ;;
|
||||||
silent-send) [ -d "$ACTUAL_HOME/silent-send/.git" ] ;;
|
silent-send) [ -d "$ACTUAL_HOME/silent-send/.git" ] ;;
|
||||||
@@ -156,7 +157,7 @@ is_installed() {
|
|||||||
# is_installed() as 0 or 1.
|
# is_installed() as 0 or 1.
|
||||||
install_count() {
|
install_count() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
base|glow|crowdsec|security-dashboard|kdeconnect|silent-send|sync-cc|claude-cli|sky-cam|sky-cam-frigate|asterisk|pstn-trunk|sms-inbound|ssh-config|ssh-key-import)
|
base|glow|crowdsec|samba|security-dashboard|kdeconnect|silent-send|sync-cc|claude-cli|sky-cam|sky-cam-frigate|asterisk|pstn-trunk|sms-inbound|ssh-config|ssh-key-import)
|
||||||
is_installed "$1" && echo 1 || echo 0 ;;
|
is_installed "$1" && echo 1 || echo 0 ;;
|
||||||
wordpress)
|
wordpress)
|
||||||
find "$DOCKER_DIR" -mindepth 1 -maxdepth 1 -name 'wordpress-*' -type d 2>/dev/null | wc -l ;;
|
find "$DOCKER_DIR" -mindepth 1 -maxdepth 1 -name 'wordpress-*' -type d 2>/dev/null | wc -l ;;
|
||||||
|
|||||||
Reference in New Issue
Block a user