Merge pull request #173 from outis1one/claude/bootstrap-script-404-d8dc7h
Add SSH Host alias management (base wizard, standalone service, docs)
This commit is contained in:
@@ -49,7 +49,7 @@ sudo ./setup.sh --unattended base # non-interactive, use defaults
|
|||||||
## What the wizard does
|
## What the wizard does
|
||||||
|
|
||||||
**First run:**
|
**First run:**
|
||||||
1. Installs essential CLI packages (`net-tools`, `ncdu`, `git`, `curl`, `wget`, `htop`, `tree`, `zip`/`unzip`, `ca-certificates`, `gnupg`, `jq`, `rsync`, `glow`), Docker CE + Compose plugin, and `openssh-server` — offers to import SSH keys from GitHub/Launchpad (`ssh-import-id`), disable password login once a key is confirmed, and install NetBird
|
1. Installs essential CLI packages (`net-tools`, `ncdu`, `git`, `curl`, `wget`, `htop`, `tree`, `zip`/`unzip`, `ca-certificates`, `gnupg`, `jq`, `rsync`, `glow`), Docker CE + Compose plugin, and `openssh-server` — offers to import SSH keys from GitHub/Launchpad (`ssh-import-id`), disable password login once a key is confirmed, install NetBird, and add SSH Host aliases (see [SSH Host aliases](#ssh-host-aliases))
|
||||||
2. Asks **where Caddy runs** — this machine, a remote machine/VPN peer, or none — before anything else, since every later service prompt depends on the answer
|
2. Asks **where Caddy runs** — this machine, a remote machine/VPN peer, or none — before anything else, since every later service prompt depends on the answer
|
||||||
3. If Caddy is local: offers to set **site defaults** — timezone, base domain, Caddy Docker network — so every service picks them up automatically instead of asking each time, then offers to install Caddy itself
|
3. If Caddy is local: offers to set **site defaults** — timezone, base domain, Caddy Docker network — so every service picks them up automatically instead of asking each time, then offers to install Caddy itself
|
||||||
4. Drops into a **category menu** — pick a group, tick services, install, repeat
|
4. Drops into a **category menu** — pick a group, tick services, install, repeat
|
||||||
@@ -66,13 +66,13 @@ a ready-to-copy Caddy config snippet to `~/docker/caddy-snippets/`.
|
|||||||
|
|
||||||
| Group | Services |
|
| 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 and optional password-auth lockdown; 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`, `sunshine` |
|
| `homelab` | `caddy`, `crowdsec`, `authelia`, `homeassistant`, `asterisk`, `sunshine` |
|
||||||
| `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` |
|
| `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` |
|
||||||
| `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` |
|
||||||
| `extras` | `kdeconnect`, `silent-send`, `sync-cc` |
|
| `extras` | `kdeconnect`, `silent-send`, `ssh-config`, `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); `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) |
|
| `backup` | `backup` — complete recovery: entire `~/docker/<service>/` for every service via Kopia (Minecraft: flush+snap, no downtime; others: stop/snap/start for DB consistency); `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.
|
Run `./setup.sh --list` to see descriptions.
|
||||||
@@ -156,6 +156,7 @@ gaming
|
|||||||
extras
|
extras
|
||||||
kdeconnect
|
kdeconnect
|
||||||
silent-send
|
silent-send
|
||||||
|
ssh-config
|
||||||
sync-cc
|
sync-cc
|
||||||
|
|
||||||
backup
|
backup
|
||||||
@@ -188,6 +189,31 @@ docker compose pull && docker compose up -d # update
|
|||||||
docker compose down # stop
|
docker compose down # stop
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSH Host aliases
|
||||||
|
|
||||||
|
`~/.ssh/config` lets you `ssh <alias>` instead of typing `ssh user@1.2.3.4`
|
||||||
|
every time — especially handy once machines are reachable over a VPN/NetBird
|
||||||
|
overlay network where the IP is easy to forget:
|
||||||
|
|
||||||
|
```
|
||||||
|
Host myserver
|
||||||
|
HostName 100.x.x.x
|
||||||
|
User someuser
|
||||||
|
Port 22
|
||||||
|
```
|
||||||
|
|
||||||
|
Three ways to manage these entries:
|
||||||
|
|
||||||
|
- **During `base` install** — after SSH key import, the wizard offers to add
|
||||||
|
one or more aliases interactively
|
||||||
|
- **Any time** — `sudo ./setup.sh ssh-config` lists, adds, or removes aliases
|
||||||
|
without touching anything else
|
||||||
|
- **By hand** — edit `~/.ssh/config` directly; it's a plain OpenSSH client
|
||||||
|
config file, nothing generated or templated beyond the `Host` block itself
|
||||||
|
|
||||||
|
Aliases are written to the invoking user's own config (not root's), since
|
||||||
|
that's whose terminal actually runs `ssh`.
|
||||||
|
|
||||||
## Installing from a USB thumb drive
|
## Installing from a USB thumb drive
|
||||||
|
|
||||||
No git required. Works for anyone with a browser.
|
No git required. Works for anyone with a browser.
|
||||||
|
|||||||
@@ -224,6 +224,85 @@ require_docker() {
|
|||||||
log_success "Docker installed ($("$_docker_bin" --version 2>/dev/null))"
|
log_success "Docker installed ($("$_docker_bin" --version 2>/dev/null))"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── SSH client config (~/.ssh/config) Host aliases ────────────────────────────
|
||||||
|
# Lets "ssh <alias>" connect directly to user@host without typing it out each
|
||||||
|
# time — handy for VPN/NetBird peers with unmemorable IPs. Operates on the
|
||||||
|
# ACTUAL_USER's config (not root's), since that's whose terminal runs ssh.
|
||||||
|
ssh_config_path() { echo "$ACTUAL_HOME/.ssh/config"; }
|
||||||
|
|
||||||
|
ssh_host_alias_exists() {
|
||||||
|
local alias="$1" cfg; cfg="$(ssh_config_path)"
|
||||||
|
[ -f "$cfg" ] && grep -qiE "^Host[[:space:]]+${alias}([[:space:]]|\$)" "$cfg"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_ssh_host_alias() {
|
||||||
|
local alias="$1" hostname="$2" user="$3" port="${4:-22}"
|
||||||
|
local cfg; cfg="$(ssh_config_path)"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo "[DRY-RUN] Would add SSH alias '$alias' -> $user@$hostname:$port to $cfg"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$cfg")"
|
||||||
|
touch "$cfg"
|
||||||
|
chmod 700 "$(dirname "$cfg")"
|
||||||
|
chmod 600 "$cfg"
|
||||||
|
|
||||||
|
if ssh_host_alias_exists "$alias"; then
|
||||||
|
log_warning "Host alias '$alias' already exists in $cfg — skipping (remove it first to replace)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "Host $alias"
|
||||||
|
echo " HostName $hostname"
|
||||||
|
echo " User $user"
|
||||||
|
[ "$port" != "22" ] && echo " Port $port"
|
||||||
|
} >> "$cfg"
|
||||||
|
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$(dirname "$cfg")" 2>/dev/null || true
|
||||||
|
log_success "Added SSH alias: ssh $alias -> $user@$hostname:$port"
|
||||||
|
}
|
||||||
|
|
||||||
|
list_ssh_host_aliases() {
|
||||||
|
local cfg; cfg="$(ssh_config_path)"
|
||||||
|
if [ ! -f "$cfg" ] || ! grep -qiE "^Host[[:space:]]+" "$cfg"; then
|
||||||
|
echo " (none — $cfg has no Host entries yet)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
grep -inE "^Host[[:space:]]+" "$cfg" | sed -E 's/^([0-9]+):Host[[:space:]]+/ \1) /'
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_ssh_host_alias() {
|
||||||
|
local alias="$1" cfg; cfg="$(ssh_config_path)"
|
||||||
|
if [ ! -f "$cfg" ]; then
|
||||||
|
log_warning "No SSH config file found at $cfg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! ssh_host_alias_exists "$alias"; then
|
||||||
|
log_warning "Host alias '$alias' not found in $cfg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo "[DRY-RUN] Would remove SSH alias '$alias' from $cfg"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local tmp; tmp="$(mktemp)"
|
||||||
|
awk -v alias="$alias" '
|
||||||
|
BEGIN { skip=0 }
|
||||||
|
tolower($1)=="host" && tolower($2)==tolower(alias) { skip=1; next }
|
||||||
|
skip==1 && /^Host[[:space:]]/ { skip=0 }
|
||||||
|
skip==1 && /^[[:space:]]*$/ { skip=0; next }
|
||||||
|
skip==1 { next }
|
||||||
|
{ print }
|
||||||
|
' "$cfg" > "$tmp"
|
||||||
|
mv "$tmp" "$cfg"
|
||||||
|
chmod 600 "$cfg"
|
||||||
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$cfg" 2>/dev/null || true
|
||||||
|
log_success "Removed SSH alias: $alias"
|
||||||
|
}
|
||||||
|
|
||||||
# ── Command execution honoring dry-run ───────────────────────────────────────
|
# ── Command execution honoring dry-run ───────────────────────────────────────
|
||||||
run_cmd() {
|
run_cmd() {
|
||||||
if [ "$DRY_RUN" = true ]; then
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ install_base() {
|
|||||||
echo "[DRY-RUN] Would install core apt packages"
|
echo "[DRY-RUN] Would install core apt packages"
|
||||||
echo "[DRY-RUN] Would install glow from Charm repo"
|
echo "[DRY-RUN] Would install glow from Charm repo"
|
||||||
echo "[DRY-RUN] Would install Docker CE + Compose plugin"
|
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 install/configure openssh-server"
|
echo "[DRY-RUN] Would install/configure openssh-server"
|
||||||
echo "[DRY-RUN] Would offer SSH key import from GitHub/Launchpad"
|
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 to disable SSH password auth"
|
||||||
echo "[DRY-RUN] Would offer NetBird install with --allow-server-ssh"
|
echo "[DRY-RUN] Would offer NetBird install with --allow-server-ssh"
|
||||||
|
echo "[DRY-RUN] Would offer to add SSH Host aliases to ~/.ssh/config"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -33,11 +36,81 @@ install_base() {
|
|||||||
# ── Docker ───────────────────────────────────────────────────────────────
|
# ── Docker ───────────────────────────────────────────────────────────────
|
||||||
require_docker || log_warning "Docker install failed — will retry after base setup"
|
require_docker || log_warning "Docker install failed — will retry after base setup"
|
||||||
|
|
||||||
|
# ── NVIDIA GPU (driver + container toolkit) ─────────────────────────────
|
||||||
|
_base_setup_nvidia_gpu
|
||||||
|
|
||||||
# ── OpenSSH server ───────────────────────────────────────────────────────
|
# ── OpenSSH server ───────────────────────────────────────────────────────
|
||||||
_base_setup_ssh
|
_base_setup_ssh
|
||||||
|
|
||||||
# ── NetBird ──────────────────────────────────────────────────────────────
|
# ── NetBird ──────────────────────────────────────────────────────────────
|
||||||
_base_setup_netbird
|
_base_setup_netbird
|
||||||
|
|
||||||
|
# ── SSH Host aliases ─────────────────────────────────────────────────────
|
||||||
|
_base_setup_ssh_aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
_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() {
|
_base_setup_ssh() {
|
||||||
@@ -124,6 +197,28 @@ _base_setup_netbird() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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.
|
# glow is also exposed as its own module so it can be (re)installed on its own.
|
||||||
install_glow() {
|
install_glow() {
|
||||||
if command -v glow >/dev/null 2>&1; then
|
if command -v glow >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# services/ssh-config.sh — manage ~/.ssh/config Host aliases.
|
||||||
|
# Part of the modular post-install system (sourced by setup.sh).
|
||||||
|
#
|
||||||
|
# Can also be run standalone on any machine:
|
||||||
|
# sudo bash ssh-config.sh
|
||||||
|
#
|
||||||
|
# Not a Docker service — edits the invoking (non-root) user's ~/.ssh/config
|
||||||
|
# so "ssh myserver" connects directly instead of "ssh user@1.2.3.4". Handy
|
||||||
|
# once NetBird/VPN peers have IPs you don't want to memorize or retype.
|
||||||
|
|
||||||
|
# ── 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; }
|
||||||
|
|
||||||
|
# Match common.sh's eval-based pattern so local vars in install_* are set correctly
|
||||||
|
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}'"
|
||||||
|
}
|
||||||
|
|
||||||
|
ssh_config_path() { echo "$ACTUAL_HOME/.ssh/config"; }
|
||||||
|
|
||||||
|
ssh_host_alias_exists() {
|
||||||
|
local alias="$1" cfg; cfg="$(ssh_config_path)"
|
||||||
|
[ -f "$cfg" ] && grep -qiE "^Host[[:space:]]+${alias}([[:space:]]|\$)" "$cfg"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_ssh_host_alias() {
|
||||||
|
local alias="$1" hostname="$2" user="$3" port="${4:-22}"
|
||||||
|
local cfg; cfg="$(ssh_config_path)"
|
||||||
|
mkdir -p "$(dirname "$cfg")"
|
||||||
|
touch "$cfg"
|
||||||
|
chmod 700 "$(dirname "$cfg")"
|
||||||
|
chmod 600 "$cfg"
|
||||||
|
if ssh_host_alias_exists "$alias"; then
|
||||||
|
log_warning "Host alias '$alias' already exists in $cfg — skipping."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "Host $alias"
|
||||||
|
echo " HostName $hostname"
|
||||||
|
echo " User $user"
|
||||||
|
[ "$port" != "22" ] && echo " Port $port"
|
||||||
|
} >> "$cfg"
|
||||||
|
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$(dirname "$cfg")" 2>/dev/null || true
|
||||||
|
log_success "Added SSH alias: ssh $alias -> $user@$hostname:$port"
|
||||||
|
}
|
||||||
|
|
||||||
|
list_ssh_host_aliases() {
|
||||||
|
local cfg; cfg="$(ssh_config_path)"
|
||||||
|
if [ ! -f "$cfg" ] || ! grep -qiE "^Host[[:space:]]+" "$cfg"; then
|
||||||
|
echo " (none — $cfg has no Host entries yet)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
grep -inE "^Host[[:space:]]+" "$cfg" | sed -E 's/^([0-9]+):Host[[:space:]]+/ \1) /'
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_ssh_host_alias() {
|
||||||
|
local alias="$1" cfg; cfg="$(ssh_config_path)"
|
||||||
|
[ -f "$cfg" ] || { log_warning "No SSH config file found at $cfg"; return 1; }
|
||||||
|
if ! ssh_host_alias_exists "$alias"; then
|
||||||
|
log_warning "Host alias '$alias' not found in $cfg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
local tmp; tmp="$(mktemp)"
|
||||||
|
awk -v alias="$alias" '
|
||||||
|
BEGIN { skip=0 }
|
||||||
|
tolower($1)=="host" && tolower($2)==tolower(alias) { skip=1; next }
|
||||||
|
skip==1 && /^Host[[:space:]]/ { skip=0 }
|
||||||
|
skip==1 && /^[[:space:]]*$/ { skip=0; next }
|
||||||
|
skip==1 { next }
|
||||||
|
{ print }
|
||||||
|
' "$cfg" > "$tmp"
|
||||||
|
mv "$tmp" "$cfg"
|
||||||
|
chmod 600 "$cfg"
|
||||||
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$cfg" 2>/dev/null || true
|
||||||
|
log_success "Removed SSH alias: $alias"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Globals — ACTUAL_USER/ACTUAL_HOME must come before anything else
|
||||||
|
# ($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}")"
|
||||||
|
DRY_RUN="${DRY_RUN:-false}"
|
||||||
|
UNATTENDED="${UNATTENDED:-false}"
|
||||||
|
|
||||||
|
register_service() { :; } # no-op — no wizard to register into
|
||||||
|
_RUN_STANDALONE=1
|
||||||
|
fi
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
register_service ssh-config extras "Manage SSH Host aliases in ~/.ssh/config (ssh <alias> instead of ssh user@ip)"
|
||||||
|
|
||||||
|
install_ssh-config() {
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo "[DRY-RUN] Would list/add/remove Host aliases in $(ssh_config_path)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Current SSH Host aliases in $(ssh_config_path):"
|
||||||
|
list_ssh_host_aliases
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local ACTION=""
|
||||||
|
echo " [1] Add an alias"
|
||||||
|
echo " [2] Remove an alias"
|
||||||
|
echo " [3] Done"
|
||||||
|
prompt_text "Choice [3]:" "3" ACTION
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
1)
|
||||||
|
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."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
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"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
local REMOVE_NAME=""
|
||||||
|
prompt_text " Alias name to remove:" "" REMOVE_NAME
|
||||||
|
[ -n "$REMOVE_NAME" ] && remove_ssh_host_alias "$REMOVE_NAME"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_info "No changes."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run immediately when executed directly (deferred until after function definition)
|
||||||
|
[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_ssh-config
|
||||||
@@ -87,6 +87,8 @@ is_installed() {
|
|||||||
silent-send) [ -d "$ACTUAL_HOME/silent-send/.git" ] ;;
|
silent-send) [ -d "$ACTUAL_HOME/silent-send/.git" ] ;;
|
||||||
sync-cc) [ -f "$ACTUAL_HOME/sync-cc/sync_cc.py" ] ;;
|
sync-cc) [ -f "$ACTUAL_HOME/sync-cc/sync_cc.py" ] ;;
|
||||||
sky-cam) [ -d "$ACTUAL_HOME/sky-cam/.git" ] ;;
|
sky-cam) [ -d "$ACTUAL_HOME/sky-cam/.git" ] ;;
|
||||||
|
sky-cam-frigate) [ -d "$ACTUAL_HOME/sky-cam/.git" ] && [ -f "$ACTUAL_HOME/sky-cam/frigate-retime.sh" ] ;;
|
||||||
|
ssh-config) false ;; # repeatable management tool, never shows [installed]
|
||||||
*) [ -e "$DOCKER_DIR/$1" ] ;;
|
*) [ -e "$DOCKER_DIR/$1" ] ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user