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.
407 lines
19 KiB
Bash
407 lines
19 KiB
Bash
#!/bin/bash
|
|
# services/base.sh — essential CLI packages, Docker, SSH hardening, and NetBird.
|
|
# Part of the modular post-install system (sourced by setup.sh).
|
|
|
|
register_service base base "Essential CLI packages (net-tools, git, htop, btop, glow, …)"
|
|
|
|
install_base() {
|
|
log_info "Installing essential packages..."
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo "[DRY-RUN] Would install core apt packages"
|
|
echo "[DRY-RUN] Would install glow from Charm repo"
|
|
echo "[DRY-RUN] Would install Docker CE + Compose plugin"
|
|
echo "[DRY-RUN] Would detect an NVIDIA GPU and offer to install the driver"
|
|
echo " + NVIDIA Container Toolkit (for GPU-accelerated Docker services)"
|
|
echo "[DRY-RUN] Would add a swapfile if RAM <= 4096MB and none exists"
|
|
echo "[DRY-RUN] Would install/configure openssh-server"
|
|
echo "[DRY-RUN] Would offer SSH key import from GitHub/Launchpad"
|
|
echo "[DRY-RUN] Would offer to disable SSH password auth"
|
|
echo "[DRY-RUN] Would offer NetBird install with --allow-server-ssh"
|
|
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 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"
|
|
return 0
|
|
fi
|
|
|
|
run_cmd apt-get update -y
|
|
|
|
# Core utilities present on every install. cifs-utils/keyutils here (not
|
|
# lazily installed on first use, the way tools/mount-network-drive.sh and
|
|
# vpn-data-mount.sh's own local-mount step would otherwise do it) so SMB
|
|
# mounts work immediately whenever they're set up later, same reasoning
|
|
# as Docker/Compose being unconditional here instead of on-demand.
|
|
# keyutils explicitly, not left to cifs-utils' Recommends — some minimal
|
|
# cloud VPS images disable install-recommends, and without keyutils'
|
|
# /etc/request-key.d handlers every mount.cifs call (guest or fully
|
|
# credentialed) fails with "mount error(79): Can not access a needed
|
|
# shared library" regardless of the password being correct.
|
|
run_cmd apt-get install -y \
|
|
net-tools ncdu git curl wget htop btop tree zip unzip \
|
|
ca-certificates gnupg jq rsync ssh-import-id cifs-utils keyutils \
|
|
|| log_warning "Some essential packages failed to install"
|
|
|
|
# glow — terminal markdown reader (charmbracelet). Not in Ubuntu repos,
|
|
# so add Charm's apt repository first.
|
|
install_glow
|
|
|
|
# ── Docker ───────────────────────────────────────────────────────────────
|
|
require_docker || log_warning "Docker install failed — will retry after base setup"
|
|
|
|
# ── Swapfile — default for every install, not just Asterisk droplets ────
|
|
ensure_swapfile
|
|
|
|
# ── NVIDIA GPU (driver + container toolkit) ─────────────────────────────
|
|
_base_setup_nvidia_gpu
|
|
|
|
# ── OpenSSH server ───────────────────────────────────────────────────────
|
|
_base_setup_ssh
|
|
|
|
# ── NetBird ──────────────────────────────────────────────────────────────
|
|
_base_setup_netbird
|
|
|
|
# ── VPN-connected data mount ────────────────────────────────────────────
|
|
# Only offered if NetBird is actually present (installed just now, or
|
|
# already there from a prior run) — chained here rather than folded into
|
|
# _base_setup_netbird itself since it's independently repeatable (see
|
|
# services/vpn-data-mount.sh's own header) and users may want to run it
|
|
# again later for another home box without re-touching NetBird at all.
|
|
_base_setup_vpn_mount
|
|
|
|
# ── Caddy + CrowdSec ──────────────────────────────────────────────────────
|
|
# Not this script's own install — just an early, recommended nudge toward
|
|
# two services most other things in this repo end up wanting (a reverse
|
|
# proxy, and something watching for brute-force/scan traffic). Both stay
|
|
# fully optional and available later from the whiptail menu either way.
|
|
local _BASE_PWD="$PWD"
|
|
_base_setup_caddy
|
|
cd "$_BASE_PWD" 2>/dev/null || true
|
|
_base_setup_crowdsec
|
|
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 ─────────────────────────────────────────────────────
|
|
_base_setup_ssh_aliases
|
|
|
|
# setup.sh tab completion and old-config-backup pruning are no longer
|
|
# called from here — setup.sh itself now runs both unconditionally on
|
|
# every invocation (see the block right after --list/--status in
|
|
# setup.sh), since install_base() only ever runs downstream of that
|
|
# point anyway (base.sh has no standalone-bootstrap block — see the
|
|
# header comment — so it's only ever reached via setup.sh's own
|
|
# dispatcher). Calling them here too would just be a redundant, harmless
|
|
# no-op given both are idempotent, but the single call site in setup.sh
|
|
# is the one that actually matters: it's what fixed a box that ran e.g.
|
|
# `sudo ./setup.sh beszel-agent` directly and never explicitly ran
|
|
# `base` first, which used to mean tab completion never got set up.
|
|
}
|
|
|
|
# Wires tools/setup-completion.bash into ACTUAL_USER's shell automatically —
|
|
# no reason to make everyone find and run this by hand when base already
|
|
# touches ~/.bashrc for other things. Idempotent (checked by grep before
|
|
# appending), so reruns don't pile up duplicate source lines.
|
|
_base_setup_tab_completion() {
|
|
local comp_script="$HERE/tools/setup-completion.bash"
|
|
[ -f "$comp_script" ] || return 0
|
|
|
|
local bashrc="$ACTUAL_HOME/.bashrc"
|
|
[ -f "$bashrc" ] || return 0
|
|
grep -qF "$comp_script" "$bashrc" 2>/dev/null && return 0
|
|
|
|
# No DRY_RUN check here — setup.sh's own top-level call site (see the
|
|
# block right after --list/--status) already skips calling this at all
|
|
# when --dry-run is set, unlike install_glow()'s check further down,
|
|
# which is independently invokable (sudo ./setup.sh glow --dry-run) and
|
|
# genuinely reachable.
|
|
{
|
|
echo ""
|
|
echo "# ubuntu-post-install: setup.sh tab completion"
|
|
echo "source $comp_script"
|
|
} >> "$bashrc"
|
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$bashrc" 2>/dev/null || true
|
|
log_success "setup.sh tab completion added to $bashrc (takes effect in new shells, or: source $bashrc)"
|
|
}
|
|
|
|
# Every service in this repo backs up a live config before overwriting it
|
|
# (Caddyfile, /etc/fstab, ...) but none of them ever clean those up
|
|
# afterward — see tools/prune-old-backups.sh's own header for the full
|
|
# reasoning. Sets up a daily systemd timer that prunes anything older than
|
|
# 30 days, always keeping at least the single newest backup per file
|
|
# regardless of age. No prompt — same reasoning as _base_setup_tab_completion
|
|
# right above: idempotent, and the safety net (only ever touches disposable
|
|
# *.backup.* files, never the newest one for anything) makes this low-stakes
|
|
# enough not to ask about, the same way that one doesn't.
|
|
_base_setup_backup_pruning() {
|
|
command -v systemctl >/dev/null 2>&1 || return 0
|
|
systemctl list-unit-files prune-old-backups.timer --no-legend 2>/dev/null | grep -q . && return 0
|
|
|
|
local prune_script="$HERE/tools/prune-old-backups.sh"
|
|
[ -f "$prune_script" ] || return 0
|
|
|
|
cat > /etc/systemd/system/prune-old-backups.service << UNIT
|
|
[Unit]
|
|
Description=Prune old *.backup.* config backups (Caddyfile, fstab, etc)
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/bin/bash ${prune_script} 30
|
|
UNIT
|
|
cat > /etc/systemd/system/prune-old-backups.timer << 'UNIT'
|
|
[Unit]
|
|
Description=Daily backup pruning
|
|
|
|
[Timer]
|
|
OnCalendar=daily
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
UNIT
|
|
systemctl daemon-reload
|
|
if systemctl enable --now prune-old-backups.timer >/dev/null 2>&1; then
|
|
log_success "Old config backups will be pruned daily, keeping 30 days (systemd timer: prune-old-backups)"
|
|
else
|
|
log_warning "Couldn't enable the pruning timer — run $prune_script manually to prune old backups."
|
|
fi
|
|
}
|
|
|
|
_base_setup_nvidia_gpu() {
|
|
# Only bother if an NVIDIA GPU is physically present — silent no-op otherwise.
|
|
command -v lspci >/dev/null 2>&1 || return 0
|
|
lspci | grep -iE '(VGA compatible controller|3D controller)' | grep -qi nvidia || return 0
|
|
|
|
log_info "NVIDIA GPU detected."
|
|
|
|
local _reboot_needed=false
|
|
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1; then
|
|
log_success "NVIDIA driver already active ($(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1))"
|
|
else
|
|
local INSTALL_DRIVER=""
|
|
prompt_yn "Install the recommended NVIDIA driver? Needed for GPU-accelerated Docker services (y/n):" "y" INSTALL_DRIVER
|
|
if [[ "$INSTALL_DRIVER" =~ ^[Yy]$ ]]; then
|
|
command -v ubuntu-drivers >/dev/null 2>&1 || run_cmd apt-get install -y ubuntu-drivers-common
|
|
log_info "Detected hardware and recommended driver:"
|
|
ubuntu-drivers devices || true
|
|
if run_cmd ubuntu-drivers autoinstall; then
|
|
log_warning "NVIDIA driver installed — a REBOOT is required before the GPU is usable."
|
|
_reboot_needed=true
|
|
else
|
|
log_warning "Driver autoinstall failed — install manually: sudo ubuntu-drivers autoinstall"
|
|
return 1
|
|
fi
|
|
else
|
|
log_info "Skipping — GPU-accelerated services (ai-gpu, wolf, etc.) need a driver first."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# NVIDIA Container Toolkit — lets Docker containers request the GPU
|
|
# (--gpus / device requests). Only useful once Docker is present.
|
|
if command -v docker >/dev/null 2>&1 && ! command -v nvidia-container-cli >/dev/null 2>&1; then
|
|
local INSTALL_TOOLKIT=""
|
|
prompt_yn "Install NVIDIA Container Toolkit so Docker services can use the GPU? (y/n):" "y" INSTALL_TOOLKIT
|
|
if [[ "$INSTALL_TOOLKIT" =~ ^[Yy]$ ]]; then
|
|
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
|
|
| gpg --dearmor --yes -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
|
|
curl -sL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
|
|
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
|
|
| tee /etc/apt/sources.list.d/nvidia-container-toolkit.list >/dev/null
|
|
run_cmd apt-get update -y
|
|
if run_cmd apt-get install -y nvidia-container-toolkit; then
|
|
run_cmd nvidia-ctk runtime configure --runtime=docker
|
|
run_cmd systemctl restart docker
|
|
log_success "NVIDIA Container Toolkit installed and Docker configured for GPU access."
|
|
else
|
|
log_warning "NVIDIA Container Toolkit install failed — GPU-accelerated Docker services will need it manually."
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ "$_reboot_needed" = true ]; then
|
|
local REBOOT_NOW=""
|
|
prompt_yn "Reboot now to finish activating the NVIDIA driver? (y/n):" "n" REBOOT_NOW
|
|
if [[ "$REBOOT_NOW" =~ ^[Yy]$ ]]; then
|
|
log_info "Rebooting..."
|
|
reboot
|
|
else
|
|
log_warning "Remember to reboot before using GPU-accelerated services."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_base_setup_ssh() {
|
|
# The real logic lives in services/ssh-key-import.sh now — pulled out so
|
|
# it can be re-run on its own later (another admin's key, a home box
|
|
# that only needs this one step, ...) instead of only ever running once
|
|
# as part of this whole required-setup flow. That file keeps its own
|
|
# register_service call and stays independently selectable; this just
|
|
# chains into it, same pattern services/asterisk.sh uses for
|
|
# security-dashboard/pstn-trunk.
|
|
if declare -F install_ssh-key-import >/dev/null 2>&1; then
|
|
install_ssh-key-import
|
|
return
|
|
fi
|
|
|
|
# Standalone `sudo bash base.sh` with no sibling services/*.sh sourced —
|
|
# degrade to just getting the SSH server itself running, skip the
|
|
# GitHub/Launchpad import convenience (needs the sibling file's fuller
|
|
# standalone stubs, not worth duplicating here for this rare a path).
|
|
log_info "Configuring SSH server..."
|
|
if ! dpkg -l openssh-server &>/dev/null; then
|
|
run_cmd apt-get install -y openssh-server
|
|
fi
|
|
run_cmd systemctl enable --now ssh
|
|
log_info "Run services/ssh-key-import.sh (or the full repo's wizard) to import keys from GitHub/Launchpad."
|
|
}
|
|
|
|
_base_setup_netbird() {
|
|
local INSTALL_NB=""
|
|
prompt_yn "Install NetBird overlay network? (y/n):" "n" INSTALL_NB
|
|
[[ "$INSTALL_NB" =~ ^[Yy]$ ]] || return 0
|
|
|
|
log_info "Installing NetBird..."
|
|
if curl -fsSL https://pkgs.netbird.io/install.sh | sh; then
|
|
log_success "NetBird installed"
|
|
else
|
|
log_warning "NetBird install failed — see https://netbird.io"
|
|
return 1
|
|
fi
|
|
|
|
local NB_SSH=""
|
|
prompt_yn "Enable NetBird's built-in SSH server (--allow-server-ssh)? (y/n):" "y" NB_SSH
|
|
|
|
local NB_KEY=""
|
|
prompt_text "NetBird setup key (blank to run 'netbird up' manually later):" "" NB_KEY
|
|
|
|
local _up_args=""
|
|
[[ "$NB_SSH" =~ ^[Yy]$ ]] && _up_args="--allow-server-ssh"
|
|
|
|
if [ -n "$NB_KEY" ]; then
|
|
if netbird up --setup-key "$NB_KEY" $_up_args; then
|
|
log_success "NetBird connected${NB_SSH:+ with SSH server enabled}"
|
|
else
|
|
log_warning "NetBird up failed — run manually: netbird up --setup-key <KEY>${NB_SSH:+ --allow-server-ssh}"
|
|
fi
|
|
else
|
|
log_info "Run when ready: netbird up${_up_args:+ $_up_args}"
|
|
fi
|
|
}
|
|
|
|
_base_setup_vpn_mount() {
|
|
command -v netbird >/dev/null 2>&1 || return 0
|
|
# 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_vpn-data-mount, so skip silently rather than error.
|
|
declare -F install_vpn-data-mount >/dev/null 2>&1 || return 0
|
|
|
|
local SETUP_MOUNT=""
|
|
prompt_yn "Mount data from a NetBird-connected home box now? (y/n):" "n" SETUP_MOUNT
|
|
[[ "$SETUP_MOUNT" =~ ^[Yy]$ ]] || return 0
|
|
install_vpn-data-mount
|
|
}
|
|
|
|
_base_setup_caddy() {
|
|
if [[ -d "$DOCKER_DIR/caddy" ]]; then
|
|
log_info "Caddy 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_caddy, so skip silently rather than error.
|
|
declare -F install_caddy &>/dev/null || return 0
|
|
|
|
local INSTALL_CADDY=""
|
|
prompt_yn "Install Caddy reverse proxy? Recommended — gives every other service here a trusted HTTPS front door. (y/n):" "y" INSTALL_CADDY
|
|
[[ "$INSTALL_CADDY" =~ ^[Yy]$ ]] || return 0
|
|
install_caddy
|
|
}
|
|
|
|
_base_setup_crowdsec() {
|
|
if command -v cscli &>/dev/null; then
|
|
log_info "CrowdSec already installed."
|
|
return 0
|
|
fi
|
|
declare -F install_crowdsec &>/dev/null || return 0
|
|
|
|
local INSTALL_CS=""
|
|
prompt_yn "Install CrowdSec intrusion prevention? Recommended — bans brute-force/scan traffic against SSH and anything Caddy fronts. (y/n):" "y" INSTALL_CS
|
|
[[ "$INSTALL_CS" =~ ^[Yy]$ ]] || return 0
|
|
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() {
|
|
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
|
|
[[ "$ADD_ALIAS" =~ ^[Yy]$ ]] || return 0
|
|
|
|
while true; do
|
|
local ALIAS_NAME="" ALIAS_HOST="" ALIAS_USER="" ALIAS_PORT=""
|
|
prompt_text " Alias name (e.g. myserver):" "" ALIAS_NAME
|
|
if [ -z "$ALIAS_NAME" ]; then
|
|
log_warning "Alias name required — skipping."
|
|
else
|
|
prompt_text " Hostname or IP to connect to (e.g. a NetBird peer IP):" "" ALIAS_HOST
|
|
prompt_text " Remote username:" "$ACTUAL_USER" ALIAS_USER
|
|
prompt_text " Port [22]:" "22" ALIAS_PORT
|
|
add_ssh_host_alias "$ALIAS_NAME" "$ALIAS_HOST" "$ALIAS_USER" "$ALIAS_PORT"
|
|
fi
|
|
local ADD_ANOTHER=""
|
|
prompt_yn " Add another alias? (y/n):" "n" ADD_ANOTHER
|
|
[[ "$ADD_ANOTHER" =~ ^[Yy]$ ]] || break
|
|
done
|
|
}
|
|
|
|
# glow is also exposed as its own module so it can be (re)installed on its own.
|
|
install_glow() {
|
|
if command -v glow >/dev/null 2>&1; then
|
|
log_success "glow already installed ($(glow --version 2>/dev/null | head -1))"
|
|
return 0
|
|
fi
|
|
log_info "Installing glow (terminal markdown reader) from the Charm apt repo..."
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo "[DRY-RUN] Would add repo.charm.sh apt repo and install glow"
|
|
return 0
|
|
fi
|
|
sudo mkdir -p /etc/apt/keyrings
|
|
if curl -fsSL https://repo.charm.sh/apt/gpg.key \
|
|
| sudo gpg --dearmor --yes -o /etc/apt/keyrings/charm.gpg; then
|
|
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" \
|
|
| sudo tee /etc/apt/sources.list.d/charm.list >/dev/null
|
|
if sudo apt-get update -y && sudo apt-get install -y glow; then
|
|
log_success "glow installed ($(glow --version 2>/dev/null | head -1))"
|
|
else
|
|
log_warning "glow install failed — see https://github.com/charmbracelet/glow"
|
|
fi
|
|
else
|
|
log_warning "Could not fetch Charm signing key — skipping glow"
|
|
fi
|
|
}
|
|
|
|
# Register glow as a standalone service too (./setup.sh glow).
|
|
register_service glow base "Terminal markdown reader (charmbracelet/glow)"
|