Merge pull request #283 from outis1one/claude/ionos-script-integration-x32ofw

Claude/ionos script integration x32ofw
This commit is contained in:
Outis
2026-08-10 14:12:40 -04:00
committed by GitHub
5 changed files with 532 additions and 50 deletions
+4 -2
View File
@@ -167,12 +167,12 @@ a ready-to-copy Caddy config snippet to `~/docker/caddy-snippets/`.
| Group | Services |
|-------|---------|
| `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`, `coturn` (shared TURN/STUN relay — Asterisk, Mattermost Calls, and future WebRTC-capable services all register a dedicated credential against one instance instead of each running its own), `homeassistant`, `asterisk`, `pstn-trunk`, `sms-inbound`, `security-dashboard`, `sunshine` |
| `homelab` | `caddy`, `crowdsec`, `authelia`, `coturn` (shared TURN/STUN relay — Asterisk, Mattermost Calls, and future WebRTC-capable services all register a dedicated credential against one instance instead of each running its own), `homeassistant`, `asterisk`, `pstn-trunk`, `sms-inbound`, `security-dashboard`, `sunshine`, `vpn-data-mount` (SMB mount from a NetBird-connected home box — SSH trust bootstrap + remote Samba setup automated over SSH; repeatable, one home box/share at a time) |
| `utilities` | `actualbudget`, `ai-gpu`, `ai-stack`, `archivebox`, `changedetection`, `ddclient`, `filebrowser`, `fmd`, `gatus`, `homebox`, `iopaint`, `joplin`, `koha`, `magicmirror`, `mail-archiver`, `mattermost`, `mealie`, `meshcentral`, `n8n`, `nextcloud`, `ntfy`, `onlyoffice`, `paintplus`, `portainer`, `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) |
| `media` | `arm`, `audiobookshelf`, `calibre-web`, `emby`, `immich`, `jellyfin`, `lyrion` |
| `cameras` | `frigate`, `frigate-audio`, `frigate-notify`, `sky-cam` |
| `gaming` | `drum-rhythm-game`, `js99er`, `kyber-launcher`, `kyber-server`, `minecraft`, `wolf`, `wolf-pair` |
| `extras` | `kdeconnect`, `silent-send`, `ssh-config`, `sync-cc` |
| `extras` | `kdeconnect`, `silent-send`, `ssh-config`, `ssh-key-import` (import SSH public keys from GitHub/Launchpad, optionally lock down password auth — same step base.sh's required setup runs, re-runnable on its own), `sync-cc` |
| `backup` | `backup` — complete recovery: entire `~/docker/<service>/` for every service via Kopia (Minecraft: flush+snap, no downtime; others: stop/snap/start for DB consistency), optional offsite mirror (`kopia repository sync-to`), plus `dr_bringup.sh` — unattended restore-everything-and-start for standing up a cold spare box; `borg-backup` — same coverage via Borg (chunk dedup, SSH remote repos, Borgmatic/Vorta compatible); `gaming-backup` — frequent game-save snapshots (Minecraft world data, emulator saves, Steam — no downtime, run hourly) |
Run `./setup.sh --list` to see descriptions.
@@ -196,6 +196,7 @@ homelab
sms-inbound
security-dashboard
sunshine
vpn-data-mount
utilities
actualbudget
@@ -262,6 +263,7 @@ extras
kdeconnect
silent-send
ssh-config
ssh-key-import
sync-cc
backup
+45 -45
View File
@@ -18,6 +18,7 @@ install_base() {
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 to add SSH Host aliases to ~/.ssh/config"
@@ -26,10 +27,14 @@ install_base() {
run_cmd apt-get update -y
# Core utilities present on every install.
# Core utilities present on every install. cifs-utils 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.
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 \
ca-certificates gnupg jq rsync ssh-import-id cifs-utils \
|| log_warning "Some essential packages failed to install"
# glow — terminal markdown reader (charmbracelet). Not in Ubuntu repos,
@@ -51,6 +56,14 @@ install_base() {
# ── 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
@@ -131,54 +144,28 @@ _base_setup_nvidia_gpu() {
}
_base_setup_ssh() {
log_info "Configuring SSH server..."
# 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
# Import SSH public keys from GitHub and/or Launchpad.
local GH_USER="" LP_USER="" _keys_imported=false
prompt_text "GitHub username to import SSH keys from (blank to skip):" "" GH_USER
if [ -n "$GH_USER" ]; then
if ssh-import-id "gh:$GH_USER"; then
log_success "Imported SSH keys from GitHub: $GH_USER"
_keys_imported=true
else
log_warning "Could not import keys from GitHub: $GH_USER"
fi
fi
prompt_text "Launchpad username to import SSH keys from (blank to skip):" "" LP_USER
if [ -n "$LP_USER" ]; then
if ssh-import-id "lp:$LP_USER"; then
log_success "Imported SSH keys from Launchpad: $LP_USER"
_keys_imported=true
else
log_warning "Could not import keys from Launchpad: $LP_USER"
fi
fi
# Only offer to disable password auth if at least one key was imported.
if [ "$_keys_imported" = true ]; then
local DISABLE_PW=""
prompt_yn "Disable SSH password authentication (key login only)? (y/n):" "y" DISABLE_PW
if [[ "$DISABLE_PW" =~ ^[Yy]$ ]]; then
sed -i \
-e 's/^#*\s*PasswordAuthentication\s.*/PasswordAuthentication no/' \
-e 's/^#*\s*KbdInteractiveAuthentication\s.*/KbdInteractiveAuthentication no/' \
/etc/ssh/sshd_config
# Ubuntu 22.04+ may also have a drop-in that re-enables password auth.
local _dropin="/etc/ssh/sshd_config.d/50-cloud-init.conf"
if [ -f "$_dropin" ]; then
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' "$_dropin"
fi
systemctl restart ssh
log_success "SSH password authentication disabled — key login only"
fi
fi
log_info "Run services/ssh-key-import.sh (or the full repo's wizard) to import keys from GitHub/Launchpad."
}
_base_setup_netbird() {
@@ -214,6 +201,19 @@ _base_setup_netbird() {
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."
+138
View File
@@ -0,0 +1,138 @@
#!/bin/bash
# services/ssh-key-import.sh — import SSH public keys from GitHub/Launchpad
# for passwordless login, and optionally lock down password auth.
# Part of the modular post-install system (sourced by setup.sh).
#
# Can also be run standalone on any machine:
# sudo bash ssh-key-import.sh
#
# Extracted out of services/base.sh's required setup (which still chains
# into this) so it can be re-run on its own — e.g. a box that already went
# through base setup but needs another admin's key added later, or a home
# box (see services/vpn-data-mount.sh) that just needs this one step and
# nothing else base.sh does.
#
# What ssh-import-id actually does: fetches the PUBLIC keys listed at
# https://github.com/<user>.keys (or https://launchpad.net/~<user>/+sshkeys
# for Launchpad — Canonical/Ubuntu's own code-hosting + bug-tracker
# platform, the "other option") over HTTPS and appends them to this box's
# ~/.ssh/authorized_keys. That's the same information already publicly
# visible on that profile page — nothing secret is transmitted, and no
# PRIVATE key ever leaves the machine that generated it. This box only
# gains the ability to authenticate INBOUND connections from whoever holds
# the matching private key; it does NOT gain that person's identity for
# OUTBOUND connections (e.g. this box still can't clone a private GitHub
# repo just because it imported someone's public key — that would need a
# separate keypair generated on this box, with ITS public half added to
# GitHub, which is a different, deliberate step).
# ── 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}'"
}
run_cmd() {
[[ "${DRY_RUN:-false}" == "true" ]] && { echo "[DRY-RUN] Would execute: $*"; return 0; }
"$@"
}
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 ssh-key-import extras "Import SSH public keys from GitHub/Launchpad for passwordless login; optionally disable password auth"
install_ssh-key-import() {
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would ensure openssh-server is installed and running"
echo "[DRY-RUN] Would offer to import SSH public keys from GitHub and/or Launchpad"
echo "[DRY-RUN] Would offer to disable SSH password authentication if any key was imported"
return 0
fi
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
local GH_USER="" LP_USER="" _keys_imported=false
prompt_text "GitHub username to import SSH keys from (blank to skip):" "" GH_USER
if [ -n "$GH_USER" ]; then
if ssh-import-id "gh:$GH_USER"; then
log_success "Imported SSH keys from GitHub: $GH_USER"
_keys_imported=true
else
log_warning "Could not import keys from GitHub: $GH_USER"
fi
fi
prompt_text "Launchpad username to import SSH keys from (blank to skip):" "" LP_USER
if [ -n "$LP_USER" ]; then
if ssh-import-id "lp:$LP_USER"; then
log_success "Imported SSH keys from Launchpad: $LP_USER"
_keys_imported=true
else
log_warning "Could not import keys from Launchpad: $LP_USER"
fi
fi
if [ "$_keys_imported" = false ]; then
log_info "No keys imported — nothing else to do."
return 0
fi
local DISABLE_PW=""
prompt_yn "Disable SSH password authentication (key login only)? (y/n):" "y" DISABLE_PW
if [[ "$DISABLE_PW" =~ ^[Yy]$ ]]; then
sed -i \
-e 's/^#*\s*PasswordAuthentication\s.*/PasswordAuthentication no/' \
-e 's/^#*\s*KbdInteractiveAuthentication\s.*/KbdInteractiveAuthentication no/' \
/etc/ssh/sshd_config
# Ubuntu 22.04+ may also have a drop-in that re-enables password auth.
local _dropin="/etc/ssh/sshd_config.d/50-cloud-init.conf"
if [ -f "$_dropin" ]; then
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' "$_dropin"
fi
systemctl restart ssh
log_success "SSH password authentication disabled — key login only"
fi
}
# Run immediately when executed directly (deferred until after function definition)
[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_ssh-key-import
+335
View File
@@ -0,0 +1,335 @@
#!/bin/bash
# services/vpn-data-mount.sh — mount SMB data from a NetBird-connected home
# box, with SSH-key bootstrap and remote Samba setup automated over SSH.
# 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, Samba, 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/share you want mounted. 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.
#
# 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); it only automates the VPS side:
# SSH trust, then using that SSH access to configure Samba on the home box
# remotely, then mounting it here.
#
# 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.
#
# The share is guest-accessible (no separate Samba username/password to
# manage) because the VPN is the actual access control here — only
# NetBird-connected peers can reach the home box's NetBird IP at all, so a
# second credential layer on top of that doesn't add real security, just
# more secrets to lose track of.
# ── 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 SMB data from a NetBird-connected home box (SSH-automated remote 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
}
# ── 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
}
# ── Remote Samba setup, driven entirely over the SSH trust above ──────────
_vdm_setup_remote_samba() {
local user="$1" host="$2" remote_path="$3" share_name="$4"
log_info "Checking Samba on the home box..."
# Every ssh call below runs as $ACTUAL_USER, same reason as _vdm_ssh_works.
if ! sudo -u "$ACTUAL_USER" ssh "${user}@${host}" 'command -v smbd >/dev/null 2>&1'; then
log_info "Installing Samba on the home box (may prompt for the sudo password there)..."
sudo -u "$ACTUAL_USER" ssh -t "${user}@${host}" 'sudo apt-get update -y && sudo apt-get install -y samba' \
|| { log_error "Remote Samba install failed."; return 1; }
else
log_success "Samba already installed on the home box."
fi
log_info "Configuring the share on the home box..."
# Idempotent: drop any prior block for this exact share name, then
# append a fresh one. Guest-accessible — see the file header for why.
local remote_cmd
remote_cmd=$(cat << REMOTECMD
set -e
sudo mkdir -p '${remote_path}'
sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.backup.\$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
sudo sed -i "/^\\[${share_name}\\]\$/,/^\$/d" /etc/samba/smb.conf
{
echo ""
echo "[${share_name}]"
echo " path = ${remote_path}"
echo " browseable = yes"
echo " read only = no"
echo " guest ok = yes"
echo " force user = \$(whoami)"
} | sudo tee -a /etc/samba/smb.conf >/dev/null
sudo systemctl restart smbd
command -v ufw >/dev/null 2>&1 && sudo ufw allow samba >/dev/null 2>&1 || true
REMOTECMD
)
if sudo -u "$ACTUAL_USER" ssh -t "${user}@${host}" "$remote_cmd"; then
log_success "Remote share [$share_name] -> $remote_path configured and smbd restarted."
else
log_error "Remote Samba configuration failed — check the output above."
return 1
fi
}
# ── Local mount + fstab ─────────────────────────────────────────────────────
_vdm_mount_local() {
local host="$1" share_name="$2" mount_point="$3" label="$4"
command -v mount.cifs >/dev/null 2>&1 || apt-get install -y cifs-utils -qq
mkdir -p "$mount_point"
local opts="guest,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 — check connectivity to $host and the remote share config."
rmdir "$mount_point" 2>/dev/null || true
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"))"
}
# ── One mount, start to finish ──────────────────────────────────────────────
_vdm_add_mount() {
echo ""
local LABEL=""
while true; do
prompt_text " Short label for this mount (e.g. 'media', 'nas-docs'):" "" 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
local HOST="" SSH_USER=""
prompt_text " Home box's NetBird IP (or hostname — check 'netbird status' on that box):" "" HOST
if [ -z "$HOST" ]; then
log_warning "No host entered — cancelling this mount."
return 1
fi
prompt_text " SSH username on the home box:" "$ACTUAL_USER" SSH_USER
_vdm_ensure_ssh_trust "$SSH_USER" "$HOST" || return 1
local REMOTE_PATH="" MOUNT_POINT=""
prompt_text " Path on the home box to share (e.g. /home/${SSH_USER}/media):" "" REMOTE_PATH
if [ -z "$REMOTE_PATH" ]; then
log_warning "No path entered — cancelling this mount."
return 1
fi
prompt_text " Local mount point:" "/mnt/${LABEL}" MOUNT_POINT
_vdm_setup_remote_samba "$SSH_USER" "$HOST" "$REMOTE_PATH" "$LABEL" || return 1
_vdm_mount_local "$HOST" "$LABEL" "$MOUNT_POINT" "$LABEL" || return 1
echo ""
log_success "Done: $HOST:$REMOTE_PATH is now mounted at $MOUNT_POINT"
echo " Manage this and other network mounts anytime with:"
echo " sudo bash tools/mount-network-drive.sh"
}
install_vpn-data-mount() {
echo ""
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ VPN Data Mount — SMB share from a NetBird-connected box ║"
echo "╚══════════════════════════════════════════════════════════╝"
if [ "$DRY_RUN" = true ]; then
echo "[DRY-RUN] Would test/set up passwordless SSH to a home box over its NetBird IP"
echo "[DRY-RUN] Would remotely install+configure Samba there for a chosen path"
echo "[DRY-RUN] Would mount it locally over CIFS and add it to /etc/fstab"
echo "[DRY-RUN] Repeatable — can be run again for additional home boxes/shares"
return 0
fi
# Every prompt below (home box IP, remote path, ...) 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 the label-validation loop below
# forever.
if [ "$UNATTENDED" = true ]; then
log_info "Skipping — needs interactive input (home box IP, path, ...). Run 'sudo ./setup.sh vpn-data-mount' without --unattended."
return 0
fi
_vdm_list_existing
while true; do
local ADD=""
prompt_yn "Add a VPN data mount now? (y/n):" "y" ADD
[[ "$ADD" =~ ^[Yy]$ ]] || break
_vdm_add_mount
local AGAIN=""
prompt_yn "Add another mount (can be from a different home box)? (y/n):" "n" AGAIN
[[ "$AGAIN" =~ ^[Yy]$ ]] || break
done
}
[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_vpn-data-mount
+10 -3
View File
@@ -101,10 +101,14 @@ is_installed() {
pstn-trunk) [ -f "$DOCKER_DIR/asterisk-digital-ocean/config/asterisk/pstn-trunk-pjsip.conf" ] || [ -f "$DOCKER_DIR/asterisk/config/asterisk/pstn-trunk-pjsip.conf" ] ;;
sms-inbound) [ -f /opt/sms-inbound/settings.env ] ;;
ssh-config) false ;; # repeatable management tool, never shows [installed]
ssh-key-import) false ;; # repeatable management tool, never shows [installed]
# Every WordPress site is named from the first one on (no plain
# $DOCKER_DIR/wordpress dir the default case below could match) —
# [installed] means "at least one site exists", not any specific one.
wordpress) compgen -G "$DOCKER_DIR/wordpress-*" >/dev/null 2>&1 ;;
# Not a Docker service — state lives in tagged /etc/fstab entries
# (services/vpn-data-mount.sh's own convention), not $DOCKER_DIR.
vpn-data-mount) grep -q '^# vpn-data-mount:' /etc/fstab 2>/dev/null ;;
*) [ -e "$DOCKER_DIR/$1" ] ;;
esac
}
@@ -114,14 +118,17 @@ is_installed() {
# CLAUDE.md (a base install plus any number of "<name>-<suffix>" siblings,
# e.g. mattermost + mattermost-team-b). Only the default case knows that
# naming convention; the specially-cased services above aren't part of the
# multi-instance pattern (wordpress is the one exception and already counts
# sites directly), so for those this just mirrors is_installed() as 0 or 1.
# multi-instance pattern (wordpress and vpn-data-mount are the exceptions
# and already count sites/mounts directly), so for those this just mirrors
# is_installed() as 0 or 1.
install_count() {
case "$1" in
base|glow|crowdsec|security-dashboard|kdeconnect|silent-send|sync-cc|sky-cam|sky-cam-frigate|asterisk|pstn-trunk|sms-inbound|ssh-config)
base|glow|crowdsec|security-dashboard|kdeconnect|silent-send|sync-cc|sky-cam|sky-cam-frigate|asterisk|pstn-trunk|sms-inbound|ssh-config|ssh-key-import)
is_installed "$1" && echo 1 || echo 0 ;;
wordpress)
find "$DOCKER_DIR" -mindepth 1 -maxdepth 1 -name 'wordpress-*' -type d 2>/dev/null | wc -l ;;
vpn-data-mount)
grep -c '^# vpn-data-mount:' /etc/fstab 2>/dev/null || echo 0 ;;
*)
local c=0
[ -e "$DOCKER_DIR/$1" ] && c=1