Merge pull request #283 from outis1one/claude/ionos-script-integration-x32ofw
Claude/ionos script integration x32ofw
This commit is contained in:
@@ -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
@@ -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."
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user